Changeset 11187 in ntrip for trunk/BNC/src


Ignore:
Timestamp:
Nov 9, 2026, 5:37:55 PM (6 hours ago)
Author:
stuerze
Message:

Structured logging improved

Location:
trunk/BNC/src
Files:
8 edited

Legend:

Unmodified
Added
Removed
  • trunk/BNC/src/bnccore.cpp

    r11185 r11187  
    4343#include <QMessageBox>
    4444#include <cmath>
     45#ifndef Q_OS_WIN
     46#include <sys/stat.h>
     47#endif
    4548
    4649#include "bnccore.h"
    … …  
    7275  _logStream   = 0;
    7376  _logFileStructured  = 0;
     77  _logStructuredInit  = false;
    7478  _logFormatStructured = t_logRecord::json;
    7579  _logLevelStructured  = t_logRecord::debug;
    … …  
    195199    delete _logStream; _logStream = 0;
    196200    delete _logFile;   _logFile   = 0;
    197     delete _logFileStructured; _logFileStructured = 0;
    198201    _logFileFlag = 1;
    199202    _fileDate = currDate;
    … …  
    214217      _logStream->setDevice(_logFile);
    215218    }
    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);
    219229      _logFormatStructured = t_logRecord::formatFromString(
    220230                               settings.value("logFormatStructured").toString());
    221231      _logLevelStructured  = t_logRecord::levelFromString(
    222232                               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
    229238  }
    230239
    … …  
    244253    _logFileStructured->write(rec.encode(_logFormatStructured, currDateTime) + "\n");
    245254    _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////////////////////////////////////////////////////////////////////////////
     262void 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;
    246282  }
    247283}
  • trunk/BNC/src/bnccore.h

    r11185 r11187  
    2525#ifndef BNCAPP_H
    2626#define BNCAPP_H
     27
     28#include <QElapsedTimer>
    2729
    2830#include "bnctime.h"
    … …  
    183185                       const QString& strV4);
    184186  void  messagePrivate(const t_logRecord& rec);
     187  void  openLogFileStructured();
    185188
    186189  QSettings::SettingsMap _settings;
    … …  
    189192  int                    _logFileFlag;
    190193  QFile*                 _logFileStructured;
     194  QString                _logFileStructuredName;
     195  bool                   _logStructuredInit;
     196  QElapsedTimer          _logStructuredCheck;
    191197  t_logRecord::e_format  _logFormatStructured;
    192198  t_logRecord::e_level   _logLevelStructured;
  • trunk/BNC/src/bncgetthread.cpp

    r11185 r11187  
    840840        t_logRecord rec(t_logRecord::info, "stream", "stream_content", _staID,
    841841                        _staID + ": Received message type " + type.toLatin1() +  size.toLatin1());
     842        rec.setStructuredMsg("Received message");
    842843        rec.add("message_type", int(decoder()->_typeList[ii]._type));
    843844        if (decoder()->_typeList[ii]._size) {
  • trunk/BNC/src/bnchelp.html

    r11185 r11187  
    27872787    For log processing tools (e.g. Grafana Loki, Elasticsearch, Vector, jq), BNC can write the same records in
    27882788    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'
    27922794    restricts the structured logfile to records of at least the selected level: 'debug' (default) writes all records,
    27932795    'info' leaves out the debug records, 'warning' writes warnings and errors, 'error' errors only.
    … …  
    27982800  <ul>
    27992801    <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>
    28012804    <li>'level': 'debug', 'info', 'warning' or 'error'; debug records exist only in BNC versions built with the
    28022805        defines BNC_DEBUG_BCE, BNC_DEBUG_OBS or BNC_DEBUG_CMB (src/src.pri),</li>
    … …  
    28062809        (SSR QC), or 'bnc' for general messages,</li>
    28072810    <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>
    28092812    <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>
    28112814    <li>'v': the version of the record layout, currently 1.</li>
    28122815  </ul>
    … …  
    28152818  </p>
    28162819  <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',
    28182821        'hdop', 'fixed' (true/false), 'fix_percent'; 'ppp_epoch_failed' (warning) if no solution was computed,</li>
    28192822    <li>'stream_format': 'decoder' (RTCM2, RTCM3, ZERO, ZERO2FILE, RTNET) and 'format' (format entry of the stream),</li>
    … …  
    28212824    <li>'stream_content': content of the stream found by the RTCM scan (see section <a href=#miscscan>2.12.3</a>), e.g. observation
    28222825        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>
    28242828    <li>'stream_outage_begin' (warning), 'stream_corrupted_begin' (warning): 'since' (UTC);
    28252829        'stream_outage_end', 'stream_corrupted_end': 'ended' and 'since' (UTC),</li>
    … …  
    28392843  </p>
    28402844  <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}
    28432847</pre>
    28442848  </p>
    … …  
    28472851  </p>
    28482852  <pre><p style="font-family:Monospace">
    2849 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
    2850 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
     2853time=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
     2854time=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
    28512855</pre>
    28522856  </p>
  • trunk/BNC/src/bnclog.cpp

    r11185 r11187  
    2121#include "bnclog.h"
    2222#include "bncutils.h"
     23#include "bnctime.h"
    2324
    2425namespace {
    … …  
    211212//
    212213////////////////////////////////////////////////////////////////////////////
     214t_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////////////////////////////////////////////////////////////////////////////
    213226const char* t_logRecord::levelString(e_level level) {
    214227  switch (level) {
    … …  
    254267  QDateTime utc = gpst.addSecs(-gnumleap(dd.year(), dd.month(), dd.day()));
    255268
    256   QByteArray msg = _msg;
     269  QByteArray msg = _msgStructured.isEmpty() ? _msg : _msgStructured;
    257270  while (msg.startsWith('\n')) {
    258271    msg.remove(0, 1);
    … …  
    266279  };
    267280  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  }
    269284  put("level",     levelString(_level), true);
    270285  put("component", _component, true);
    271286  put("event",     _event, true);
    272287  if (!_stream.isEmpty()) {
    273     put("stream", _stream, true);
     288    put("mp", _stream, true);
    274289  }
    275290  all += _fields;
  • trunk/BNC/src/bnclog.h

    r11185 r11187  
    3131#include <QVector>
    3232
     33class bncTime;
     34
    3335// One record of the structured logfile (option logFileStructured).
    3436//
    … …  
    3638// logfile gets one line per record, JSON (JSON Lines) or logfmt:
    3739//
    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())
    3946//
    4047// Messages that are still emitted as plain text (newMessage(QByteArray,bool))
    … …  
    7582  t_logRecord& addTime(const char* key, const QDateTime& utc);   // ISO 8601, "Z"
    7683
    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")
    7893  QByteArray encode(e_format format, const QDateTime& gpst) const;
    7994
    … …  
    89104  QByteArray _component;
    90105  QByteArray _event;
     106  QByteArray _epoch;
    91107  QByteArray _stream;
    92108  QByteArray _msg;
     109  QByteArray _msgStructured;
    93110
    94111 private:
  • trunk/BNC/src/bncwindow.cpp

    r11185 r11187  
    15891589  // ------------------
    15901590  _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>"));
    15921592  _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>"));
    15931593  _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  
    465465      }
    466466      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);
    469471      }
    470472      else {
    471473        t_logRecord rec(t_logRecord::info, "ppp", "ppp_position", staID,
    472474                        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)
    475476           .add("x", output._xyzRover[0], 4)
    476477           .add("y", output._xyzRover[1], 4)
Note: See TracChangeset for help on using the changeset viewer.