Changeset 11187 in ntrip
- Timestamp:
- Nov 9, 2026, 5:37:55 PM (less than one hour ago)
- Location:
- trunk/BNC
- Files:
-
- 15 edited
-
CHANGELOG.md (modified) (1 diff)
-
src/bnccore.cpp (modified) (5 diffs)
-
src/bnccore.h (modified) (3 diffs)
-
src/bncgetthread.cpp (modified) (1 diff)
-
src/bnchelp.html (modified) (7 diffs)
-
src/bnclog.cpp (modified) (4 diffs)
-
src/bnclog.h (modified) (4 diffs)
-
src/bncwindow.cpp (modified) (1 diff)
-
src/pppRun.cpp (modified) (1 diff)
-
tests/README.md (modified) (1 diff)
-
tests/cases/log_structured/case.json (modified) (1 diff)
-
tests/cases/log_structured/expected/PPPQuickStart.jsonl.gz (modified) ( previous)
-
tests/cases/log_structured/expected/PPPQuickStart.logfmt.gz (modified) ( previous)
-
tests/cases/log_structured/expected/logcheck.txt.gz (modified) ( previous)
-
tests/tools/logcheck.py (modified) (5 diffs)
Legend:
- Unmodified
- Added
- Removed
-
trunk/BNC/CHANGELOG.md
r11185 r11187 1 1 # Changelog 2 2 ## 2.13.8 (2026-11-04) 3 - ADDED: **Structured logfile** (General options 'Structured logfile', 'Format' and 'Min. level', keys logFileStructured, logFormatStructured and logLevelStructured): in addition to the unchanged logfile, the same records as JSON Lines ('json', default) or key=value pairs ('logfmt'), daily file '_YYMMDD' like the logfile; each record hastime in UTC ('time')andGPS Time ('gpst'), level, component, event,stream, the event's fields, the logfile text ('msg') and a version ('v'). Events with fields: ppp_position, ppp_epoch_failed, stream_format, stream_content (RTCM scan, with message_type and size of the received message types), stream_timeout, stream_outage_begin/end, stream_corrupted_begin/end, stream_latency; all other messages as event 'message' with the component of their origin (stream, ntrip, bce, ssr, ppp, cmb, upload, ssrqc, bnc) and a level (error: a function does not work; warning: data or configuration problem, BNC continues; info: status); messages of the BNC_DEBUG_BCE/OBS/CMB defines with level 'debug' and component bce/obs/cmb. Test case log_structured and check tests/tools/logcheck.py3 - ADDED: **Structured logfile** (General options 'Structured logfile', 'Format' and 'Min. level', keys logFileStructured, logFormatStructured and logLevelStructured): in addition to the unchanged logfile, the same records as JSON Lines ('json', default) or key=value pairs ('logfmt'), one appended file without date for rotation by logrotate & co (reopened if moved or removed); each record has the time in UTC ('time'), for records of a data epoch its GPS Time ('epoch'), level, component, event, mountpoint ('mp'), the event's fields, the logfile text ('msg') and a version ('v'). Events with fields: ppp_position, ppp_epoch_failed, stream_format, stream_content (RTCM scan, with message_type and size of the received message types and short msg 'Received message'), stream_timeout, stream_outage_begin/end, stream_corrupted_begin/end, stream_latency; all other messages as event 'message' with the component of their origin (stream, ntrip, bce, ssr, ppp, cmb, upload, ssrqc, bnc) and a level (error: a function does not work; warning: data or configuration problem, BNC continues; info: status); messages of the BNC_DEBUG_BCE/OBS/CMB defines with level 'debug' and component bce/obs/cmb. Test case log_structured and check tests/tools/logcheck.py 4 4 - FIXED: Build with BNC_DEBUG_BCE: undeclared message number in the debug messages of the Galileo ephemeris decoder 5 5 - ADDED: **epoch/velocity fields to PPP coordinates file to allow BNC to propagate the reference coordinates** related to a dedicated reference epoch to the current processing epoch internally; coordinates can also be given as latitude/longitude/height -
trunk/BNC/src/bnccore.cpp
r11185 r11187 43 43 #include <QMessageBox> 44 44 #include <cmath> 45 #ifndef Q_OS_WIN 46 #include <sys/stat.h> 47 #endif 45 48 46 49 #include "bnccore.h" … … 72 75 _logStream = 0; 73 76 _logFileStructured = 0; 77 _logStructuredInit = false; 74 78 _logFormatStructured = t_logRecord::json; 75 79 _logLevelStructured = t_logRecord::debug; … … 195 199 delete _logStream; _logStream = 0; 196 200 delete _logFile; _logFile = 0; 197 delete _logFileStructured; _logFileStructured = 0;198 201 _logFileFlag = 1; 199 202 _fileDate = currDate; … … 214 217 _logStream->setDevice(_logFile); 215 218 } 216 QString structName = settings.value("logFileStructured").toString(); 217 if ( !structName.isEmpty() ) { 218 expandEnvVar(structName); 219 } 220 221 // Structured logfile: one endless file (rotation by external tools) 222 // --------------------------------------------------------------- 223 if (!_logStructuredInit) { 224 _logStructuredInit = true; 225 bncSettings settings; 226 _logFileStructuredName = settings.value("logFileStructured").toString(); 227 if (!_logFileStructuredName.isEmpty()) { 228 expandEnvVar(_logFileStructuredName); 219 229 _logFormatStructured = t_logRecord::formatFromString( 220 230 settings.value("logFormatStructured").toString()); 221 231 _logLevelStructured = t_logRecord::levelFromString( 222 232 settings.value("logLevelStructured").toString()); 223 _logFileStructured = new QFile(structName + "_" + currDate.toString("yyMMdd")); 224 if (!_logFileStructured->open(append ? (QIODevice::WriteOnly | QIODevice::Append) 225 : QIODevice::WriteOnly)) { 226 delete _logFileStructured; _logFileStructured = 0; 227 } 228 } 233 openLogFileStructured(); 234 } 235 } 236 else if (!_logFileStructuredName.isEmpty() && _logStructuredCheck.hasExpired(1000)) { 237 openLogFileStructured(); // reopens only if the file was moved or removed 229 238 } 230 239 … … 244 253 _logFileStructured->write(rec.encode(_logFormatStructured, currDateTime) + "\n"); 245 254 _logFileStructured->flush(); 255 } 256 } 257 258 // Open the structured logfile, or reopen it after an external rotation 259 // (logrotate without copytruncate moves the file and the old one must be 260 // released); always appending, also after copytruncate 261 //////////////////////////////////////////////////////////////////////////// 262 void t_bncCore::openLogFileStructured() { 263 _logStructuredCheck.start(); 264 if (_logFileStructured) { 265 bool same = _logFileStructured->exists(); 266 #ifndef Q_OS_WIN 267 if (same) { 268 struct stat stPath, stOpen; 269 same = (stat(QFile::encodeName(_logFileStructuredName).constData(), &stPath) == 0 && 270 fstat(_logFileStructured->handle(), &stOpen) == 0 && 271 stPath.st_dev == stOpen.st_dev && stPath.st_ino == stOpen.st_ino); 272 } 273 #endif 274 if (same) { 275 return; 276 } 277 delete _logFileStructured; _logFileStructured = 0; 278 } 279 _logFileStructured = new QFile(_logFileStructuredName); 280 if (!_logFileStructured->open(QIODevice::WriteOnly | QIODevice::Append)) { 281 delete _logFileStructured; _logFileStructured = 0; 246 282 } 247 283 } -
trunk/BNC/src/bnccore.h
r11185 r11187 25 25 #ifndef BNCAPP_H 26 26 #define BNCAPP_H 27 28 #include <QElapsedTimer> 27 29 28 30 #include "bnctime.h" … … 183 185 const QString& strV4); 184 186 void messagePrivate(const t_logRecord& rec); 187 void openLogFileStructured(); 185 188 186 189 QSettings::SettingsMap _settings; … … 189 192 int _logFileFlag; 190 193 QFile* _logFileStructured; 194 QString _logFileStructuredName; 195 bool _logStructuredInit; 196 QElapsedTimer _logStructuredCheck; 191 197 t_logRecord::e_format _logFormatStructured; 192 198 t_logRecord::e_level _logLevelStructured; -
trunk/BNC/src/bncgetthread.cpp
r11185 r11187 840 840 t_logRecord rec(t_logRecord::info, "stream", "stream_content", _staID, 841 841 _staID + ": Received message type " + type.toLatin1() + size.toLatin1()); 842 rec.setStructuredMsg("Received message"); 842 843 rec.add("message_type", int(decoder()->_typeList[ii]._type)); 843 844 if (decoder()->_typeList[ii]._size) { -
trunk/BNC/src/bnchelp.html
r11185 r11187 2787 2787 For log processing tools (e.g. Grafana Loki, Elasticsearch, Vector, jq), BNC can write the same records in 2788 2788 addition into a structured logfile: specify its path in the 'Structured logfile' field and select the format, 2789 'json' for one JSON object per line (JSON Lines) or 'logfmt' for key=value pairs separated by blanks. As for the 2790 logfile, the name is extended by '_YYMMDD'. The logfile itself remains unchanged, so existing scripts reading it 2791 keep working. Default is an empty option field, meaning that no structured logfile is written. 'Min. level' 2789 'json' for one JSON object per line (JSON Lines) or 'logfmt' for key=value pairs separated by blanks. Unlike the 2790 logfile, the structured logfile is one file without date in its name, always appended (also without 'Append 2791 files'), meant to be rotated by an external tool such as logrotate: if the file is moved or removed, BNC 2792 continues with a new file of the same name within one second; 'copytruncate' works as well. The logfile itself 2793 remains unchanged, so existing scripts reading it keep working. Default is an empty option field, meaning that no structured logfile is written. 'Min. level' 2792 2794 restricts the structured logfile to records of at least the selected level: 'debug' (default) writes all records, 2793 2795 'info' leaves out the debug records, 'warning' writes warnings and errors, 'error' errors only. … … 2798 2800 <ul> 2799 2801 <li>'time': time of the record in UTC, ISO 8601 with 'Z', e.g. '2025-11-17T13:49:40.000Z',</li> 2800 <li>'gpst': the same time in GPS Time (as the time stamps of the logfile), e.g. '2025-11-17T13:49:58.000',</li> 2802 <li>'epoch': GPS Time of the data epoch the record refers to, ISO 8601 without 'Z', e.g. '2025-11-17T13:49:10.000'; 2803 only in records of an epoch ('ppp_position', 'ppp_epoch_failed'),</li> 2801 2804 <li>'level': 'debug', 'info', 'warning' or 'error'; debug records exist only in BNC versions built with the 2802 2805 defines BNC_DEBUG_BCE, BNC_DEBUG_OBS or BNC_DEBUG_CMB (src/src.pri),</li> … … 2806 2809 (SSR QC), or 'bnc' for general messages,</li> 2807 2810 <li>'event': the kind of record, see below, or 'message' for general messages,</li> 2808 <li>' stream':the stream(mountpoint)or the PPP station, if known,</li>2811 <li>'mp': the mountpoint of the stream or the PPP station, if known,</li> 2809 2812 <li>the fields of the event,</li> 2810 <li>'msg': the text of the record in the logfile,</li> 2813 <li>'msg': the text of the record in the logfile (shortened where the fields carry the details),</li> 2811 2814 <li>'v': the version of the record layout, currently 1.</li> 2812 2815 </ul> … … 2815 2818 </p> 2816 2819 <ul> 2817 <li>'ppp_position': ' epoch' (GPS Time), 'x', 'y', 'z', 'dn', 'de', 'du', 'trp0', 'trp', 'trp_stdev', 'num_sat',2820 <li>'ppp_position': 'x', 'y', 'z', 'dn', 'de', 'du', 'trp0', 'trp', 'trp_stdev', 'num_sat', 2818 2821 'hdop', 'fixed' (true/false), 'fix_percent'; 'ppp_epoch_failed' (warning) if no solution was computed,</li> 2819 2822 <li>'stream_format': 'decoder' (RTCM2, RTCM3, ZERO, ZERO2FILE, RTNET) and 'format' (format entry of the stream),</li> … … 2821 2824 <li>'stream_content': content of the stream found by the RTCM scan (see section <a href=#miscscan>2.12.3</a>), e.g. observation 2822 2825 types, antenna and receiver descriptors, antenna coordinates, GLONASS slots, CRS and Helmert parameters; for the 2823 received message types with the fields 'message_type' and, if known, 'size' (bytes),</li> 2826 received message types with the fields 'message_type' and, if known, 'size' (bytes) and the short 'msg' 2827 'Received message' (logfile: 'Received message type 1127 (size 618)'),</li> 2824 2828 <li>'stream_outage_begin' (warning), 'stream_corrupted_begin' (warning): 'since' (UTC); 2825 2829 'stream_outage_end', 'stream_corrupted_end': 'ended' and 'since' (UTC),</li> … … 2839 2843 </p> 2840 2844 <pre><p style="font-family:Monospace"> 2841 {"time":"2025-11-17T13:48:51.000Z"," gpst":"2025-11-17T13:49:09.000","level":"info","component":"stream","event":"stream_format","stream":"BCEP00BKG0","decoder":"RTCM3","format":"RTCM_3.3","msg":"BCEP00BKG0: Get data in RTCM 3.x format","v":1}2842 {"time":"2025-11-17T13:49:40.000Z"," gpst":"2025-11-17T13:49:58.000","level":"info","component":"ppp","event":"ppp_position","stream":"WTZR00DEU0","epoch":"2025-11-17T13:49:10.000","x":4075580.2172,"y":931854.1555,"z":4801568.3403,"dn":0.0000,"de":0.0000,"du":0.0001,"trp0":2.1833,"trp":0.0019,"trp_stdev":0.0993,"num_sat":17,"hdop":0.59,"fixed":false,"fix_percent":0,"msg":"2025-11-17_13:49:10.000 WTZR00DEU0 X = 4075580.2172 ...","v":1}2845 {"time":"2025-11-17T13:48:51.000Z","level":"info","component":"stream","event":"stream_format","mp":"BCEP00BKG0","decoder":"RTCM3","format":"RTCM_3.3","msg":"BCEP00BKG0: Get data in RTCM 3.x format","v":1} 2846 {"time":"2025-11-17T13:49:40.000Z","epoch":"2025-11-17T13:49:10.000","level":"info","component":"ppp","event":"ppp_position","mp":"WTZR00DEU0","x":4075580.2172,"y":931854.1555,"z":4801568.3403,"dn":0.0000,"de":0.0000,"du":0.0001,"trp0":2.1833,"trp":0.0019,"trp_stdev":0.0993,"num_sat":17,"hdop":0.59,"fixed":false,"fix_percent":0,"msg":"2025-11-17_13:49:10.000 WTZR00DEU0 X = 4075580.2172 ...","v":1} 2843 2847 </pre> 2844 2848 </p> … … 2847 2851 </p> 2848 2852 <pre><p style="font-family:Monospace"> 2849 time=2025-11-17T13:48:51.000Z gpst=2025-11-17T13:49:09.000level=info component=stream event=stream_formatstream=BCEP00BKG0 decoder=RTCM3 format=RTCM_3.3 msg="BCEP00BKG0: Get data in RTCM 3.x format" v=12850 time=2025-11-17T13:49:40.000Z gpst=2025-11-17T13:49:58.000 level=info component=ppp event=ppp_positionstream=WTZR00DEU0 epoch=2025-11-17T13:49:10.000 x=4075580.2172 y=931854.1555 z=4801568.3403 dn=0.0000 de=0.0000 du=0.0001 trp0=2.1833 trp=0.0019 trp_stdev=0.0993 num_sat=17 hdop=0.59 fixed=false fix_percent=0 msg="2025-11-17_13:49:10.000 WTZR00DEU0 X = 4075580.2172 ..." v=12853 time=2025-11-17T13:48:51.000Z level=info component=stream event=stream_format mp=BCEP00BKG0 decoder=RTCM3 format=RTCM_3.3 msg="BCEP00BKG0: Get data in RTCM 3.x format" v=1 2854 time=2025-11-17T13:49:40.000Z epoch=2025-11-17T13:49:10.000 level=info component=ppp event=ppp_position mp=WTZR00DEU0 x=4075580.2172 y=931854.1555 z=4801568.3403 dn=0.0000 de=0.0000 du=0.0001 trp0=2.1833 trp=0.0019 trp_stdev=0.0993 num_sat=17 hdop=0.59 fixed=false fix_percent=0 msg="2025-11-17_13:49:10.000 WTZR00DEU0 X = 4075580.2172 ..." v=1 2851 2855 </pre> 2852 2856 </p> -
trunk/BNC/src/bnclog.cpp
r11185 r11187 21 21 #include "bnclog.h" 22 22 #include "bncutils.h" 23 #include "bnctime.h" 23 24 24 25 namespace { … … 211 212 // 212 213 //////////////////////////////////////////////////////////////////////////// 214 t_logRecord& t_logRecord::setEpoch(const bncTime& epoch) { 215 if (epoch.valid()) { 216 _epoch = QByteArray((epoch.datestr() + 'T' + epoch.timestr(3)).c_str()); 217 } 218 else { 219 _epoch.clear(); 220 } 221 return *this; 222 } 223 224 // 225 //////////////////////////////////////////////////////////////////////////// 213 226 const char* t_logRecord::levelString(e_level level) { 214 227 switch (level) { … … 254 267 QDateTime utc = gpst.addSecs(-gnumleap(dd.year(), dd.month(), dd.day())); 255 268 256 QByteArray msg = _msg; 269 QByteArray msg = _msgStructured.isEmpty() ? _msg : _msgStructured; 257 270 while (msg.startsWith('\n')) { 258 271 msg.remove(0, 1); … … 266 279 }; 267 280 put("time", utc.toString("yyyy-MM-ddThh:mm:ss.zzz").toLatin1() + "Z", true); 268 put("gpst", gpst.toString("yyyy-MM-ddThh:mm:ss.zzz").toLatin1(), true); 281 if (!_epoch.isEmpty()) { 282 put("epoch", _epoch, true); 283 } 269 284 put("level", levelString(_level), true); 270 285 put("component", _component, true); 271 286 put("event", _event, true); 272 287 if (!_stream.isEmpty()) { 273 put(" stream", _stream, true);288 put("mp", _stream, true); 274 289 } 275 290 all += _fields; -
trunk/BNC/src/bnclog.h
r11185 r11187 31 31 #include <QVector> 32 32 33 class bncTime; 34 33 35 // One record of the structured logfile (option logFileStructured). 34 36 // … … 36 38 // logfile gets one line per record, JSON (JSON Lines) or logfmt: 37 39 // 38 // time gpst level component event [stream] [fields ...] msg v 40 // time [epoch] level component event [mp] [fields ...] msg v 41 // 42 // mp: mountpoint of the stream or PPP station (member _stream) 43 // 44 // time: time of the record in UTC; epoch: GPS Time of the data epoch the 45 // record refers to (e.g. PPP position), only if set (setEpoch()) 39 46 // 40 47 // Messages that are still emitted as plain text (newMessage(QByteArray,bool)) … … 75 82 t_logRecord& addTime(const char* key, const QDateTime& utc); // ISO 8601, "Z" 76 83 77 // one line without line end; gpst: time of the record in GPS time 84 // shorter text for the structured logfile only, e.g. if the fields carry 85 // the details; the classic logfile keeps _msg 86 t_logRecord& setStructuredMsg(const QByteArray& msg) { _msgStructured = msg; return *this; } 87 88 // data epoch of the record (GPS Time, ISO 8601 without "Z"), key "epoch" 89 t_logRecord& setEpoch(const bncTime& epoch); 90 91 // one line without line end; gpst: time of the record in GPS Time, 92 // written as UTC (key "time") 78 93 QByteArray encode(e_format format, const QDateTime& gpst) const; 79 94 … … 89 104 QByteArray _component; 90 105 QByteArray _event; 106 QByteArray _epoch; 91 107 QByteArray _stream; 92 108 QByteArray _msg; 109 QByteArray _msgStructured; 93 110 94 111 private: -
trunk/BNC/src/bncwindow.cpp
r11185 r11187 1589 1589 // ------------------ 1590 1590 _logFileLineEdit->setWhatsThis(tr("<p>Records of BNC's activities are shown in the 'Log' tab on the bottom of this window. They can be saved into a file when a valid path for that is specified in the 'Logfile (full path)' field.</p><p>The logfile name will automatically be extended by a string '_YYMMDD' carrying the current date. <i>[key: logFile]</i></p>")); 1591 _logFileStructuredLineEdit->setWhatsThis(tr("<p>In addition to the logfile, BNC can write the same records as a structured logfile for log processing tools: one record per line, either JSON (JSON Lines) or logfmt (key=value pairs separated by blanks). Each record carries the time in UTC ('time', ISO 8601 with 'Z') and in GPS time ('gpst'), a level (debug, info, warning, error), the component (e.g. stream, ppp, cmb), the event (e.g. ppp_position, stream_outage_begin), the stream (mountpoint) or PPP station if known, the event's fields, the text of the classic logfile ('msg') and the record version ('v').</p><p>Messages without own event are written with event 'message' and the component of their origin; their level is error (a function does not work), warning (data or configuration problem, BNC continues) or info (status). The logfile itself remains unchanged.</p><p>The file name will automatically be extended by a string '_YYMMDD' carrying the current date. Default is an empty option field, meaning that no structured logfile is written. <i>[key: logFileStructured]</i></p>"));1591 _logFileStructuredLineEdit->setWhatsThis(tr("<p>In addition to the logfile, BNC can write the same records as a structured logfile for log processing tools: one record per line, either JSON (JSON Lines) or logfmt (key=value pairs separated by blanks). Each record carries the time in UTC ('time', ISO 8601 with 'Z'), for records of a data epoch its GPS Time ('epoch'), a level (debug, info, warning, error), the component (e.g. stream, ppp, cmb), the event (e.g. ppp_position, stream_outage_begin), the mountpoint ('mp') of the stream or the PPP station if known, the event's fields, the text of the classic logfile ('msg') and the record version ('v').</p><p>Messages without own event are written with event 'message' and the component of their origin; their level is error (a function does not work), warning (data or configuration problem, BNC continues) or info (status). The logfile itself remains unchanged.</p><p>The structured logfile is one file without date in its name, always appended, to be rotated by an external tool like logrotate (BNC continues with a new file if it is moved or removed). Default is an empty option field, meaning that no structured logfile is written. <i>[key: logFileStructured]</i></p>")); 1592 1592 _logFormatStructuredComboBox->setWhatsThis(tr("<p>Select the format of the structured logfile: 'json' for one JSON object per line (JSON Lines), 'logfmt' for key=value pairs. Default is 'json'. <i>[key: logFormatStructured]</i></p>")); 1593 1593 _logLevelStructuredComboBox->setWhatsThis(tr("<p>Select the minimum level of the records written into the structured logfile: 'debug' writes all records (debug records exist only in BNC versions built with the BNC_DEBUG_* defines), 'info' leaves out the debug records, 'warning' writes warnings and errors only, 'error' errors only. The logfile is not affected. Default is 'debug'. <i>[key: logLevelStructured]</i></p>")); -
trunk/BNC/src/pppRun.cpp
r11185 r11187 465 465 } 466 466 if (output._error) { 467 emit newLogRecord(t_logRecord(t_logRecord::warning, "ppp", "ppp_epoch_failed", 468 staID, QByteArray(log.str().c_str())), true); 467 t_logRecord rec(t_logRecord::warning, "ppp", "ppp_epoch_failed", staID, 468 QByteArray(log.str().c_str())); 469 rec.setEpoch(output._epoTime); 470 emit newLogRecord(rec, true); 469 471 } 470 472 else { 471 473 t_logRecord rec(t_logRecord::info, "ppp", "ppp_position", staID, 472 474 QByteArray(log.str().c_str())); 473 rec.add("epoch", QByteArray((output._epoTime.datestr() + 'T' + 474 output._epoTime.timestr(3)).c_str())) 475 rec.setEpoch(output._epoTime) 475 476 .add("x", output._xyzRover[0], 4) 476 477 .add("y", output._xyzRover[1], 4) -
trunk/BNC/tests/README.md
r11179 r11187 33 33 | `tools/net_check.py` | UDP/RTP sender filter and upload status checks on loopback (not part of `run_tests.py`) | 34 34 | `tools/cred_check.py` | credentials files (`~/.netrc`, BNC format) for download and upload (not part of `run_tests.py`) | 35 | `tools/logcheck.py` | structured logfile: JSON/logfmt syntax, both formats equal, `time` = `gpst`- leap seconds, `msg` = classic logfile |35 | `tools/logcheck.py` | structured logfile: JSON/logfmt syntax, both formats equal, `time` = classic GPS time stamp - leap seconds, optional `epoch`, `msg` = classic logfile | 36 36 | `work/` | scratch output, not versioned | 37 37 -
trunk/BNC/tests/cases/log_structured/case.json
r11185 r11187 24 24 "-key", "logFormatStructured", "logfmt"]}, 25 25 {"dir": "chk", "tool": ["logcheck.py", "Output/PPPQuickStart.log_251117", 26 "Output/PPPQuickStart.jsonl _251117",27 "lf/Output/PPPQuickStart.logfmt _251117"]}],26 "Output/PPPQuickStart.jsonl", 27 "lf/Output/PPPQuickStart.logfmt"]}], 28 28 "outputs": [ 29 {"glob": "Output/PPPQuickStart.jsonl _251117", "name": "PPPQuickStart.jsonl"},30 {"glob": "lf/Output/PPPQuickStart.logfmt _251117", "name": "PPPQuickStart.logfmt"},29 {"glob": "Output/PPPQuickStart.jsonl", "name": "PPPQuickStart.jsonl"}, 30 {"glob": "lf/Output/PPPQuickStart.logfmt", "name": "PPPQuickStart.logfmt"}, 31 31 {"glob": "chk/stdout.txt", "name": "logcheck.txt"} 32 32 ] -
trunk/BNC/tests/tools/logcheck.py
r11179 r11187 6 6 Checks that every line of the JSON file is a JSON object and every line of the 7 7 logfmt file a sequence of key=value pairs, that both carry the same records, 8 that the records have the mandatory keys in the documented order, that 'time' 9 (UTC) is 'gpst' minus the GPS-UTC leap seconds and that the 'msg' of the 10 records equals the text of the classic logfile line by line (classic lines 11 without their "yy-mm-dd hh:mm:ss " prefix; the start line is skipped, it goes 12 to the file of the current date). Prints a summary of the records per level, 8 that the records have the mandatory keys in the documented order (optional 9 'epoch' after 'time', GPS Time without 'Z'), that the 'msg' of the records 10 (for RTCM scan message types: 'mp', 'message_type', 'size') equals the text of 11 the classic logfile line by line (classic lines without 12 their "yy-mm-dd hh:mm:ss " prefix; the start line is skipped, it goes to the 13 file of the current date) and that 'time' (UTC) is the GPS Time stamp of the 14 classic line minus the GPS-UTC leap seconds. Prints a summary of the records per level, 13 15 component and event; exit code 1 on any error. 14 16 """ … … 20 22 import sys 21 23 22 HEAD = ["time", " gpst", "level", "component", "event"]24 HEAD = ["time", "level", "component", "event"] 23 25 LEVELS = {"info", "warning", "error"} 24 26 LEAP_GPS_UTC = 18 # valid since 2017-01-01 … … 68 70 69 71 72 def classic_text(r): 73 """Text of the classic logfile line of a record: 'msg', except for the 74 message types of the RTCM scan, which carry the short text 'Received message' 75 and the details in their fields.""" 76 if r.get("event") == "stream_content" and "message_type" in r: 77 if r["msg"] != "Received message": 78 return "unexpected msg %r" % r["msg"] 79 size = " (size %d)" % r["size"] if "size" in r else " " 80 return "%s: Received message type %d%s" % (r["mp"], r["message_type"], size) 81 return r.get("msg") 82 83 70 84 def main(): 71 85 errors = [] 72 classic =[]86 classic, stamps = [], [] 73 87 for line in open(sys.argv[1], encoding="latin-1", newline="").read().split("\r\n"): 74 88 if not line or "========== Start BNC" in line: 75 89 continue 76 classic.append(line[18:] if re.match(r"\d\d-\d\d-\d\d \d\d:\d\d:\d\d ", line) else line) 90 if re.match(r"\d\d-\d\d-\d\d \d\d:\d\d:\d\d ", line): 91 classic.append(line[18:]) 92 stamps.append(datetime.datetime.strptime(line[:17], "%y-%m-%d %H:%M:%S")) 93 else: 94 classic.append(line) 95 stamps.append(None) 77 96 78 97 recs = [] … … 92 111 errors.append("json %d records, logfmt %d" % (len(recs), len(lf))) 93 112 for k, (a, b) in enumerate(zip(recs, lf), 1): 94 if list(a.keys()) != list(b.keys()) or any(a[x] != b[x] for x in a): 113 # the start line carries the wall clock time of each run 114 start = "========== Start BNC" in a.get("msg", "") 115 if list(a.keys()) != list(b.keys()) or any(a[x] != b[x] for x in a 116 if not (start and x == "time")): 95 117 errors.append("record %d differs between json and logfmt" % k) 96 118 … … 98 120 for k, r in enumerate(recs, 1): 99 121 keys = list(r.keys()) 100 if keys[:5] != HEAD or keys[-2:] != ["msg", "v"]: 122 if keys[1:2] == ["epoch"]: 123 try: 124 datetime.datetime.strptime(r["epoch"], "%Y-%m-%dT%H:%M:%S.%f") 125 except ValueError: 126 errors.append("record %d: epoch %s" % (k, r["epoch"])) 127 keys = keys[:1] + keys[2:] 128 if keys[:4] != HEAD or keys[-2:] != ["msg", "v"]: 101 129 errors.append("record %d: keys %s" % (k, keys)) 102 130 continue 103 131 if r["level"] not in LEVELS: 104 132 errors.append("record %d: level %s" % (k, r["level"])) 105 utc = datetime.datetime.strptime(r["time"], "%Y-%m-%dT%H:%M:%S.%fZ")106 gps = datetime.datetime.strptime(r["gpst"], "%Y-%m-%dT%H:%M:%S.%f")107 if (gps - utc).total_seconds() != LEAP_GPS_UTC:108 errors.append("record %d: time %s gpst %s" % (k, r["time"], r["gpst"]))109 133 count[(r["level"], r["component"], r["event"])] += 1 110 134 111 msgs = [r.get("msg") for r in recs if "========== Start BNC" not in r.get("msg", "")] 135 body = [r for r in recs if "========== Start BNC" not in r.get("msg", "")] 136 msgs = [classic_text(r) for r in body] 137 for k, (r, st) in enumerate(zip(body, stamps), 1): 138 utc = datetime.datetime.strptime(r["time"], "%Y-%m-%dT%H:%M:%S.%fZ") 139 if st is not None and (st - utc.replace(microsecond=0)).total_seconds() != LEAP_GPS_UTC: 140 errors.append("record %d: time %s, classic %s" % (k, r["time"], st)) 112 141 if msgs != classic: 113 142 for k, (a, b) in enumerate(zip(msgs, classic), 1):
Note:
See TracChangeset
for help on using the changeset viewer.
