• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

tstack / lnav / 17589970077-2502

09 Sep 2025 05:00PM UTC coverage: 65.196% (-5.0%) from 70.225%
17589970077-2502

push

github

tstack
[format] add fields for source file/line

Knowing the source file/line context in a log
message can help find log messages when using
log2src.

56 of 70 new or added lines in 2 files covered. (80.0%)

13954 existing lines in 210 files now uncovered.

45516 of 69814 relevant lines covered (65.2%)

404154.37 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

84.08
/src/log_format.cc
1
/**
2
 * Copyright (c) 2007-2015, Timothy Stack
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 * * Redistributions of source code must retain the above copyright notice, this
10
 * list of conditions and the following disclaimer.
11
 * * Redistributions in binary form must reproduce the above copyright notice,
12
 * this list of conditions and the following disclaimer in the documentation
13
 * and/or other materials provided with the distribution.
14
 * * Neither the name of Timothy Stack nor the names of its contributors
15
 * may be used to endorse or promote products derived from this software
16
 * without specific prior written permission.
17
 *
18
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
19
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21
 * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
22
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
 */
29

30
#include <algorithm>
31
#include <memory>
32

33
#include <fnmatch.h>
34
#include <stdio.h>
35
#include <string.h>
36

37
#include "base/fs_util.hh"
38
#include "base/is_utf8.hh"
39
#include "base/itertools.enumerate.hh"
40
#include "base/itertools.hh"
41
#include "base/map_util.hh"
42
#include "base/opt_util.hh"
43
#include "base/snippet_highlighters.hh"
44
#include "base/string_util.hh"
45
#include "command_executor.hh"
46
#include "config.h"
47
#include "fmt/format.h"
48
#include "lnav_util.hh"
49
#include "log_format_ext.hh"
50
#include "log_search_table.hh"
51
#include "log_vtab_impl.hh"
52
#include "ptimec.hh"
53
#include "readline_highlighters.hh"
54
#include "scn/scan.h"
55
#include "spookyhash/SpookyV2.h"
56
#include "sql_util.hh"
57
#include "sqlite-extension-func.hh"
58
#include "sqlitepp.hh"
59
#include "yajlpp/yajlpp.hh"
60
#include "yajlpp/yajlpp_def.hh"
61

62
using namespace lnav::roles::literals;
63

64
static auto intern_lifetime = intern_string::get_table_lifetime();
65

66
constexpr string_attr_type<void> L_PREFIX("prefix");
67
constexpr string_attr_type<void> L_TIMESTAMP("timestamp");
68
constexpr string_attr_type<void> L_LEVEL("level");
69
constexpr string_attr_type<std::shared_ptr<logfile>> L_FILE("file");
70
constexpr string_attr_type<bookmark_metadata*> L_PARTITION("partition");
71
constexpr string_attr_type<void> L_MODULE("module");
72
constexpr string_attr_type<void> L_OPID("opid");
73
constexpr string_attr_type<bookmark_metadata*> L_META("meta");
74

75
external_log_format::mod_map_t external_log_format::MODULE_FORMATS;
76
std::vector<std::shared_ptr<external_log_format>>
77
    external_log_format::GRAPH_ORDERED_FORMATS;
78

79
const intern_string_t log_format::LOG_TIME_STR
80
    = intern_string::lookup("log_time");
81
const intern_string_t log_format::LOG_LEVEL_STR
82
    = intern_string::lookup("log_level");
83
const intern_string_t log_format::LOG_OPID_STR
84
    = intern_string::lookup("log_opid");
85

86
static constexpr uint32_t DATE_TIME_SET_FLAGS = ETF_YEAR_SET | ETF_MONTH_SET
87
    | ETF_DAY_SET | ETF_HOUR_SET | ETF_MINUTE_SET | ETF_SECOND_SET;
88

89
log_level_stats&
90
log_level_stats::operator|=(const log_level_stats& rhs)
353✔
91
{
92
    this->lls_error_count += rhs.lls_error_count;
353✔
93
    this->lls_warning_count += rhs.lls_warning_count;
353✔
94
    this->lls_total_count += rhs.lls_total_count;
353✔
95

96
    return *this;
353✔
97
}
98

99
log_op_description&
100
log_op_description::operator|=(const log_op_description& rhs)
353✔
101
{
102
    if (!this->lod_id && rhs.lod_id) {
353✔
UNCOV
103
        this->lod_id = rhs.lod_id;
×
104
    }
105
    if (this->lod_elements.size() < rhs.lod_elements.size()) {
353✔
106
        this->lod_elements = rhs.lod_elements;
×
107
    }
108

109
    return *this;
353✔
110
}
111

112
void
UNCOV
113
opid_time_range::clear()
×
114
{
UNCOV
115
    this->otr_range.invalidate();
×
UNCOV
116
    this->otr_sub_ops.clear();
×
UNCOV
117
    this->otr_level_stats = {};
×
118
}
119

120
opid_time_range&
121
opid_time_range::operator|=(const opid_time_range& rhs)
353✔
122
{
123
    this->otr_range |= rhs.otr_range;
353✔
124
    this->otr_description |= rhs.otr_description;
353✔
125
    this->otr_level_stats |= rhs.otr_level_stats;
353✔
126
    for (const auto& rhs_sub : rhs.otr_sub_ops) {
357✔
127
        bool found = false;
4✔
128

129
        for (auto& sub : this->otr_sub_ops) {
8✔
130
            if (sub.ostr_subid == rhs_sub.ostr_subid) {
4✔
131
                sub.ostr_range |= rhs_sub.ostr_range;
4✔
132
                found = true;
4✔
133
            }
134
        }
135
        if (!found) {
4✔
UNCOV
136
            this->otr_sub_ops.emplace_back(rhs_sub);
×
137
        }
138
    }
139
    std::stable_sort(this->otr_sub_ops.begin(), this->otr_sub_ops.end());
353✔
140

141
    return *this;
353✔
142
}
143

144
void
145
log_level_stats::update_msg_count(log_level_t lvl, int32_t amount)
9,354✔
146
{
147
    switch (lvl) {
9,354✔
148
        case LEVEL_FATAL:
501✔
149
        case LEVEL_CRITICAL:
150
        case LEVEL_ERROR:
151
            this->lls_error_count += amount;
501✔
152
            break;
501✔
153
        case LEVEL_WARNING:
16✔
154
            this->lls_warning_count += amount;
16✔
155
            break;
16✔
156
        default:
8,837✔
157
            break;
8,837✔
158
    }
159
    this->lls_total_count += amount;
9,354✔
160
}
9,354✔
161

162
void
UNCOV
163
opid_time_range::close_sub_ops(const string_fragment& subid)
×
164
{
UNCOV
165
    for (auto& other_sub : this->otr_sub_ops) {
×
UNCOV
166
        if (other_sub.ostr_subid == subid) {
×
UNCOV
167
            other_sub.ostr_open = false;
×
168
        }
169
    }
170
}
171

172
log_opid_map::iterator
173
log_opid_state::insert_op(ArenaAlloc::Alloc<char>& alloc,
9,305✔
174
                          const string_fragment& opid,
175
                          const struct timeval& log_tv)
176
{
177
    auto retval = this->los_opid_ranges.find(opid);
9,305✔
178
    if (retval == this->los_opid_ranges.end()) {
9,305✔
179
        auto opid_copy = opid.to_owned(alloc);
3,478✔
180
        auto otr = opid_time_range{time_range{log_tv, log_tv}};
3,478✔
181
        auto emplace_res = this->los_opid_ranges.emplace(opid_copy, otr);
3,478✔
182
        retval = emplace_res.first;
3,478✔
183
    } else {
3,478✔
184
        retval->second.otr_range.extend_to(log_tv);
5,827✔
185
    }
186

187
    return retval;
9,305✔
188
}
189

190
opid_sub_time_range*
191
log_opid_state::sub_op_in_use(ArenaAlloc::Alloc<char>& alloc,
49✔
192
                              log_opid_map::iterator& op_iter,
193
                              const string_fragment& subid,
194
                              const timeval& tv,
195
                              log_level_t level)
196
{
197
    const auto& opid = op_iter->first;
49✔
198
    auto sub_iter = this->los_sub_in_use.find(subid);
49✔
199
    if (sub_iter == this->los_sub_in_use.end()) {
49✔
200
        auto emp_res
201
            = this->los_sub_in_use.emplace(subid.to_owned(alloc), opid);
33✔
202

203
        sub_iter = emp_res.first;
33✔
204
    }
205

206
    auto retval = sub_iter->first;
49✔
207
    if (sub_iter->second != opid) {
49✔
208
        auto other_otr
UNCOV
209
            = lnav::map::find(this->los_opid_ranges, sub_iter->second);
×
UNCOV
210
        if (other_otr) {
×
UNCOV
211
            other_otr->get().close_sub_ops(retval);
×
212
        }
213
    }
214
    sub_iter->second = opid;
49✔
215

216
    auto& otr = op_iter->second;
49✔
217
    auto sub_op_iter = otr.otr_sub_ops.rbegin();
49✔
218
    for (; sub_op_iter != otr.otr_sub_ops.rend(); ++sub_op_iter) {
49✔
219
        if (sub_op_iter->ostr_open && sub_op_iter->ostr_subid == retval) {
16✔
220
            break;
16✔
221
        }
222
    }
223
    if (sub_op_iter == otr.otr_sub_ops.rend()) {
49✔
224
        otr.otr_sub_ops.emplace_back(opid_sub_time_range{
33✔
225
            retval,
226
            time_range{tv, tv},
227
        });
228
        otr.otr_sub_ops.back().ostr_level_stats.update_msg_count(level);
33✔
229

230
        return &otr.otr_sub_ops.back();
33✔
231
    } else {
232
        sub_op_iter->ostr_range.extend_to(tv);
16✔
233
        sub_op_iter->ostr_level_stats.update_msg_count(level);
16✔
234
        return &(*sub_op_iter);
16✔
235
    }
236
}
237

238
std::optional<std::string>
239
log_format::opid_descriptor::matches(const string_fragment& sf) const
278✔
240
{
241
    if (this->od_extractor.pp_value) {
278✔
242
        thread_local auto desc_md = lnav::pcre2pp::match_data::unitialized();
278✔
243

244
        auto desc_match_res = this->od_extractor.pp_value->capture_from(sf)
278✔
245
                                  .into(desc_md)
278✔
246
                                  .matches(PCRE2_NO_UTF_CHECK | PCRE2_ANCHORED)
556✔
247
                                  .ignore_error();
278✔
248
        if (desc_match_res) {
278✔
249
            return desc_md.to_string();
53✔
250
        }
251

252
        return std::nullopt;
225✔
253
    }
UNCOV
254
    return sf.to_string();
×
255
}
256

257
std::string
258
log_format::opid_descriptors::to_string(
1,027✔
259
    const lnav::map::small<size_t, std::string>& lod) const
260
{
261
    std::string retval;
1,027✔
262

263
    for (size_t lpc = 0; lpc < this->od_descriptors->size(); lpc++) {
2,054✔
264
        retval.append(this->od_descriptors->at(lpc).od_prefix);
1,027✔
265
        auto iter = lod.find(lpc);
1,027✔
266
        if (iter != lod.end()) {
1,027✔
267
            retval.append(iter->second);
1,027✔
268
        }
269
        retval.append(this->od_descriptors->at(lpc).od_suffix);
1,027✔
270
    }
271

272
    return retval;
1,027✔
UNCOV
273
}
×
274

275
chart_type_t
276
logline_value_meta::to_chart_type() const
8✔
277
{
278
    auto retval = chart_type_t::hist;
8✔
279
    switch (this->lvm_kind) {
8✔
280
        case value_kind_t::VALUE_NULL:
×
UNCOV
281
            retval = chart_type_t::none;
×
UNCOV
282
            break;
×
283
        case value_kind_t::VALUE_INTEGER:
4✔
284
            if (!this->lvm_identifier && !this->lvm_foreign_key) {
4✔
285
                retval = chart_type_t::spectro;
2✔
286
            }
287
            break;
4✔
UNCOV
288
        case value_kind_t::VALUE_FLOAT:
×
UNCOV
289
            retval = chart_type_t::spectro;
×
UNCOV
290
            break;
×
291
        case value_kind_t::VALUE_XML:
1✔
292
        case value_kind_t::VALUE_JSON:
293
        case value_kind_t::VALUE_BOOLEAN:
294
        case value_kind_t::VALUE_TIMESTAMP:
295
            retval = chart_type_t::none;
1✔
296
            break;
1✔
297
        default:
3✔
298
            break;
3✔
299
    }
300

301
    return retval;
8✔
302
}
303

304
struct line_range
305
logline_value::origin_in_full_msg(const char* msg, ssize_t len) const
×
306
{
UNCOV
307
    if (this->lv_sub_offset == 0) {
×
308
        return this->lv_origin;
×
309
    }
310

UNCOV
311
    if (len == -1) {
×
312
        len = strlen(msg);
×
313
    }
314

315
    struct line_range retval = this->lv_origin;
×
316
    const char *last = msg, *msg_end = msg + len;
×
317

UNCOV
318
    for (int lpc = 0; lpc < this->lv_sub_offset; lpc++) {
×
UNCOV
319
        const auto* next = (const char*) memchr(last, '\n', msg_end - last);
×
320
        require(next != nullptr);
×
321

UNCOV
322
        next += 1;
×
323
        int amount = (next - last);
×
324

UNCOV
325
        retval.lr_start += amount;
×
326
        if (retval.lr_end != -1) {
×
327
            retval.lr_end += amount;
×
328
        }
329

UNCOV
330
        last = next + 1;
×
331
    }
332

333
    if (retval.lr_end == -1) {
×
UNCOV
334
        const auto* eol = (const char*) memchr(last, '\n', msg_end - last);
×
335

UNCOV
336
        if (eol == nullptr) {
×
UNCOV
337
            retval.lr_end = len;
×
338
        } else {
UNCOV
339
            retval.lr_end = eol - msg;
×
340
        }
341
    }
342

343
    return retval;
×
344
}
345

346
logline_value::logline_value(logline_value_meta lvm,
610,176✔
347
                             shared_buffer_ref& sbr,
348
                             struct line_range origin)
610,176✔
349
    : lv_meta(std::move(lvm)), lv_origin(origin)
610,176✔
350
{
351
    if (sbr.get_data() == nullptr) {
610,176✔
UNCOV
352
        this->lv_meta.lvm_kind = value_kind_t::VALUE_NULL;
×
353
    }
354

355
    switch (this->lv_meta.lvm_kind) {
610,176✔
356
        case value_kind_t::VALUE_JSON:
307,686✔
357
        case value_kind_t::VALUE_XML:
358
        case value_kind_t::VALUE_STRUCT:
359
        case value_kind_t::VALUE_TEXT:
360
        case value_kind_t::VALUE_QUOTED:
361
        case value_kind_t::VALUE_W3C_QUOTED:
362
        case value_kind_t::VALUE_TIMESTAMP:
363
            require(origin.lr_end != -1);
307,686✔
364
            this->lv_frag = string_fragment::from_byte_range(
307,686✔
365
                sbr.get_data(), origin.lr_start, origin.lr_end);
307,686✔
366
            break;
307,686✔
367

UNCOV
368
        case value_kind_t::VALUE_NULL:
×
UNCOV
369
            break;
×
370

371
        case value_kind_t::VALUE_INTEGER: {
273,423✔
372
            auto scan_res
373
                = scn::scan_value<int64_t>(sbr.to_string_view(origin));
273,423✔
374
            if (scan_res) {
273,423✔
375
                this->lv_value.i = scan_res->value();
273,421✔
376
            } else {
377
                this->lv_value.i = 0;
2✔
378
            }
379
            break;
273,423✔
380
        }
381

382
        case value_kind_t::VALUE_FLOAT: {
29,067✔
383
            auto scan_res = scn::scan_value<double>(sbr.to_string_view(origin));
29,067✔
384
            if (scan_res) {
29,067✔
385
                this->lv_value.d = scan_res->value();
29,067✔
386
            } else {
387
                this->lv_value.d = 0;
×
388
            }
389
            break;
29,067✔
390
        }
391

UNCOV
392
        case value_kind_t::VALUE_BOOLEAN:
×
393
            if (strncmp(
×
UNCOV
394
                    sbr.get_data_at(origin.lr_start), "true", origin.length())
×
395
                    == 0
UNCOV
396
                || strncmp(
×
397
                       sbr.get_data_at(origin.lr_start), "yes", origin.length())
×
398
                    == 0)
399
            {
UNCOV
400
                this->lv_value.i = 1;
×
401
            } else {
UNCOV
402
                this->lv_value.i = 0;
×
403
            }
UNCOV
404
            break;
×
405

UNCOV
406
        case value_kind_t::VALUE_UNKNOWN:
×
407
        case value_kind_t::VALUE__MAX:
UNCOV
408
            ensure(0);
×
409
            break;
410
    }
411
}
610,176✔
412

413
std::string
414
logline_value::to_string() const
5,933✔
415
{
416
    char buffer[128];
417

418
    switch (this->lv_meta.lvm_kind) {
5,933✔
419
        case value_kind_t::VALUE_NULL:
6✔
420
            return "null";
12✔
421

422
        case value_kind_t::VALUE_JSON:
5,682✔
423
        case value_kind_t::VALUE_XML:
424
        case value_kind_t::VALUE_STRUCT:
425
        case value_kind_t::VALUE_TEXT:
426
        case value_kind_t::VALUE_TIMESTAMP:
427
            if (this->lv_str) {
5,682✔
428
                return this->lv_str.value();
1,416✔
429
            }
430
            if (this->lv_frag.empty()) {
4,266✔
431
                return this->lv_intern_string.to_string();
20✔
432
            }
433
            return this->lv_frag.to_string();
4,246✔
434

435
        case value_kind_t::VALUE_QUOTED:
7✔
436
        case value_kind_t::VALUE_W3C_QUOTED:
437
            if (this->lv_frag.empty()) {
7✔
UNCOV
438
                return "";
×
439
            } else {
440
                switch (this->lv_frag.data()[0]) {
7✔
441
                    case '\'':
7✔
442
                    case '"': {
443
                        auto unquote_func = this->lv_meta.lvm_kind
14✔
444
                                == value_kind_t::VALUE_W3C_QUOTED
445
                            ? unquote_w3c
7✔
446
                            : unquote;
447
                        stack_buf allocator;
7✔
448
                        auto* unquoted_str
449
                            = allocator.allocate(this->lv_frag.length());
7✔
450
                        size_t unquoted_len;
451

452
                        unquoted_len = unquote_func(unquoted_str,
7✔
453
                                                    this->lv_frag.data(),
454
                                                    this->lv_frag.length());
7✔
455
                        return {unquoted_str, unquoted_len};
14✔
456
                    }
7✔
457
                    default:
×
458
                        return this->lv_frag.to_string();
×
459
                }
460
            }
461
            break;
462

463
        case value_kind_t::VALUE_INTEGER:
238✔
464
            snprintf(buffer, sizeof(buffer), "%" PRId64, this->lv_value.i);
238✔
465
            break;
238✔
466

467
        case value_kind_t::VALUE_FLOAT:
×
UNCOV
468
            snprintf(buffer, sizeof(buffer), "%lf", this->lv_value.d);
×
469
            break;
×
470

UNCOV
471
        case value_kind_t::VALUE_BOOLEAN:
×
UNCOV
472
            if (this->lv_value.i) {
×
UNCOV
473
                return "true";
×
474
            } else {
UNCOV
475
                return "false";
×
476
            }
477
            break;
UNCOV
478
        case value_kind_t::VALUE_UNKNOWN:
×
479
        case value_kind_t::VALUE__MAX:
UNCOV
480
            ensure(0);
×
481
            break;
482
    }
483

484
    return {buffer};
476✔
485
}
486

487
string_fragment
488
logline_value::to_string_fragment(ArenaAlloc::Alloc<char>& alloc) const
1,089✔
489
{
490
    char buffer[128];
491

492
    switch (this->lv_meta.lvm_kind) {
1,089✔
UNCOV
493
        case value_kind_t::VALUE_NULL:
×
UNCOV
494
            return "null"_frag;
×
495

496
        case value_kind_t::VALUE_JSON:
1,089✔
497
        case value_kind_t::VALUE_XML:
498
        case value_kind_t::VALUE_STRUCT:
499
        case value_kind_t::VALUE_TEXT:
500
        case value_kind_t::VALUE_TIMESTAMP:
501
            if (this->lv_str) {
1,089✔
UNCOV
502
                return string_fragment::from_str(this->lv_str.value())
×
UNCOV
503
                    .to_owned(alloc);
×
504
            }
505
            if (this->lv_frag.empty()) {
1,089✔
UNCOV
506
                return this->lv_intern_string.to_string_fragment().to_owned(
×
UNCOV
507
                    alloc);
×
508
            }
509
            return this->lv_frag.to_owned(alloc);
1,089✔
510

UNCOV
511
        case value_kind_t::VALUE_QUOTED:
×
512
        case value_kind_t::VALUE_W3C_QUOTED:
UNCOV
513
            if (this->lv_frag.empty()) {
×
UNCOV
514
                return string_fragment{};
×
515
            } else {
UNCOV
516
                switch (this->lv_frag.data()[0]) {
×
UNCOV
517
                    case '\'':
×
518
                    case '"': {
UNCOV
519
                        auto unquote_func = this->lv_meta.lvm_kind
×
520
                                == value_kind_t::VALUE_W3C_QUOTED
UNCOV
521
                            ? unquote_w3c
×
522
                            : unquote;
UNCOV
523
                        stack_buf allocator;
×
524
                        auto* unquoted_str
UNCOV
525
                            = allocator.allocate(this->lv_frag.length());
×
526
                        size_t unquoted_len;
527

UNCOV
528
                        unquoted_len = unquote_func(unquoted_str,
×
529
                                                    this->lv_frag.data(),
UNCOV
530
                                                    this->lv_frag.length());
×
UNCOV
531
                        return string_fragment::from_bytes(unquoted_str,
×
532
                                                           unquoted_len)
UNCOV
533
                            .to_owned(alloc);
×
534
                    }
UNCOV
535
                    default:
×
UNCOV
536
                        return this->lv_frag.to_owned(alloc);
×
537
                }
538
            }
539
            break;
540

541
        case value_kind_t::VALUE_INTEGER:
×
UNCOV
542
            snprintf(buffer, sizeof(buffer), "%" PRId64, this->lv_value.i);
×
UNCOV
543
            break;
×
544

545
        case value_kind_t::VALUE_FLOAT:
×
UNCOV
546
            snprintf(buffer, sizeof(buffer), "%lf", this->lv_value.d);
×
UNCOV
547
            break;
×
548

UNCOV
549
        case value_kind_t::VALUE_BOOLEAN:
×
UNCOV
550
            if (this->lv_value.i) {
×
UNCOV
551
                return "true"_frag;
×
552
            }
UNCOV
553
            return "false"_frag;
×
554
            break;
555
        case value_kind_t::VALUE_UNKNOWN:
×
556
        case value_kind_t::VALUE__MAX:
UNCOV
557
            ensure(0);
×
558
            break;
559
    }
560

UNCOV
561
    return string_fragment::from_c_str(buffer).to_owned(alloc);
×
562
}
563

564
std::vector<std::shared_ptr<log_format>> log_format::lf_root_formats;
565

566
std::vector<std::shared_ptr<log_format>>&
567
log_format::get_root_formats()
14,975✔
568
{
569
    return lf_root_formats;
14,975✔
570
}
571

572
void
573
external_log_format::update_op_description(
4,760✔
574
    const std::map<intern_string_t, opid_descriptors>& desc_defs,
575
    log_op_description& lod,
576
    const pattern* fpat,
577
    const lnav::pcre2pp::match_data& md)
578
{
579
    std::optional<std::string> desc_elem_str;
4,760✔
580
    if (!lod.lod_id) {
4,760✔
581
        for (const auto& desc_def_pair : desc_defs) {
4,968✔
582
            if (lod.lod_id) {
244✔
583
                break;
20✔
584
            }
585
            for (const auto& desc_def : *desc_def_pair.second.od_descriptors) {
533✔
586
                auto desc_field_index_iter = fpat->p_value_name_to_index.find(
309✔
587
                    desc_def.od_field.pp_value);
309✔
588

589
                if (desc_field_index_iter == fpat->p_value_name_to_index.end())
309✔
590
                {
591
                    continue;
35✔
592
                }
593
                auto desc_cap_opt = md[desc_field_index_iter->second];
305✔
594

595
                if (!desc_cap_opt) {
305✔
596
                    continue;
31✔
597
                }
598

599
                desc_elem_str = desc_def.matches(desc_cap_opt.value());
274✔
600
                if (desc_elem_str) {
274✔
601
                    lod.lod_id = desc_def_pair.first;
52✔
602
                }
603
            }
604
        }
605
    }
606
    if (lod.lod_id) {
4,760✔
607
        const auto& desc_def_v
608
            = *desc_defs.find(lod.lod_id.value())->second.od_descriptors;
68✔
609
        auto& desc_v = lod.lod_elements;
68✔
610

611
        if (desc_def_v.size() == desc_v.size()) {
68✔
612
            return;
16✔
613
        }
614
        for (size_t desc_def_index = 0; desc_def_index < desc_def_v.size();
107✔
615
             desc_def_index++)
616
        {
617
            const auto& desc_def = desc_def_v[desc_def_index];
55✔
618
            auto found_desc = desc_v.begin();
55✔
619

620
            for (; found_desc != desc_v.end(); ++found_desc) {
58✔
621
                if (found_desc->first == desc_def_index) {
3✔
UNCOV
622
                    break;
×
623
                }
624
            }
625
            auto desc_field_index_iter
626
                = fpat->p_value_name_to_index.find(desc_def.od_field.pp_value);
55✔
627

628
            if (desc_field_index_iter == fpat->p_value_name_to_index.end()) {
55✔
UNCOV
629
                continue;
×
630
            }
631
            auto desc_cap_opt = md[desc_field_index_iter->second];
55✔
632
            if (!desc_cap_opt) {
55✔
UNCOV
633
                continue;
×
634
            }
635

636
            if (!desc_elem_str) {
55✔
637
                desc_elem_str = desc_def.matches(desc_cap_opt.value());
4✔
638
            }
639
            if (desc_elem_str) {
55✔
640
                if (found_desc == desc_v.end()) {
52✔
641
                    desc_v.emplace_back(desc_def_index, desc_elem_str.value());
52✔
UNCOV
642
                } else if (!desc_elem_str->empty()) {
×
UNCOV
643
                    found_desc->second.append(desc_def.od_joiner);
×
UNCOV
644
                    found_desc->second.append(desc_elem_str.value());
×
645
                }
646
            }
647
            desc_elem_str = std::nullopt;
55✔
648
        }
649
    }
650
}
4,760✔
651

652
void
653
external_log_format::update_op_description(
423✔
654
    const std::map<intern_string_t, opid_descriptors>& desc_defs,
655
    log_op_description& lod)
656
{
657
    std::optional<std::string> desc_elem_str;
423✔
658
    if (!lod.lod_id) {
423✔
659
        for (const auto& desc_def_pair : desc_defs) {
423✔
UNCOV
660
            if (lod.lod_id) {
×
UNCOV
661
                break;
×
662
            }
UNCOV
663
            for (const auto& desc_def : *desc_def_pair.second.od_descriptors) {
×
664
                auto desc_cap_iter
UNCOV
665
                    = this->lf_desc_captures.find(desc_def.od_field.pp_value);
×
666

UNCOV
667
                if (desc_cap_iter == this->lf_desc_captures.end()) {
×
UNCOV
668
                    continue;
×
669
                }
UNCOV
670
                desc_elem_str = desc_def.matches(desc_cap_iter->second);
×
UNCOV
671
                if (desc_elem_str) {
×
UNCOV
672
                    lod.lod_id = desc_def_pair.first;
×
673
                }
674
            }
675
        }
676
    }
677
    if (lod.lod_id) {
423✔
678
        const auto& desc_def_v
UNCOV
679
            = *desc_defs.find(lod.lod_id.value())->second.od_descriptors;
×
UNCOV
680
        auto& desc_v = lod.lod_elements;
×
681

UNCOV
682
        if (desc_def_v.size() != desc_v.size()) {
×
UNCOV
683
            for (size_t desc_def_index = 0; desc_def_index < desc_def_v.size();
×
684
                 desc_def_index++)
685
            {
UNCOV
686
                const auto& desc_def = desc_def_v[desc_def_index];
×
UNCOV
687
                auto found_desc = desc_v.begin();
×
688

UNCOV
689
                for (; found_desc != desc_v.end(); ++found_desc) {
×
UNCOV
690
                    if (found_desc->first == desc_def_index) {
×
UNCOV
691
                        break;
×
692
                    }
693
                }
694
                auto desc_cap_iter
UNCOV
695
                    = this->lf_desc_captures.find(desc_def.od_field.pp_value);
×
UNCOV
696
                if (desc_cap_iter == this->lf_desc_captures.end()) {
×
UNCOV
697
                    continue;
×
698
                }
699

UNCOV
700
                if (!desc_elem_str) {
×
UNCOV
701
                    desc_elem_str = desc_def.matches(desc_cap_iter->second);
×
702
                }
UNCOV
703
                if (desc_elem_str) {
×
UNCOV
704
                    if (found_desc == desc_v.end()) {
×
UNCOV
705
                        desc_v.emplace_back(desc_def_index,
×
706
                                            desc_elem_str.value());
UNCOV
707
                    } else if (!desc_elem_str->empty()) {
×
UNCOV
708
                        found_desc->second.append(desc_def.od_joiner);
×
UNCOV
709
                        found_desc->second.append(desc_elem_str.value());
×
710
                    }
711
                }
UNCOV
712
                desc_elem_str = std::nullopt;
×
713
            }
714
        }
715
    }
716
}
423✔
717

718
static bool
719
next_format(
1,945,146✔
720
    const std::vector<std::shared_ptr<external_log_format::pattern>>& patterns,
721
    int& index,
722
    int& locked_index)
723
{
724
    bool retval = true;
1,945,146✔
725

726
    if (locked_index == -1) {
1,945,146✔
727
        index += 1;
1,938,093✔
728
        if (index >= (int) patterns.size()) {
1,938,093✔
729
            retval = false;
586,405✔
730
        }
731
    } else if (index == locked_index) {
7,053✔
732
        retval = false;
1✔
733
    } else {
734
        index = locked_index;
7,052✔
735
    }
736

737
    return retval;
1,945,146✔
738
}
739

740
bool
741
log_format::next_format(const pcre_format* fmt, int& index, int& locked_index)
131,972✔
742
{
743
    bool retval = true;
131,972✔
744

745
    if (locked_index == -1) {
131,972✔
746
        index += 1;
131,822✔
747
        if (fmt[index].name == nullptr) {
131,822✔
748
            retval = false;
8,867✔
749
        }
750
    } else if (index == locked_index) {
150✔
751
        retval = false;
24✔
752
    } else {
753
        index = locked_index;
126✔
754
    }
755

756
    return retval;
131,972✔
757
}
758

759
const char*
760
log_format::log_scanf(uint32_t line_number,
10,380✔
761
                      string_fragment line,
762
                      const pcre_format* fmt,
763
                      const char* time_fmt[],
764
                      struct exttm* tm_out,
765
                      struct timeval* tv_out,
766

767
                      string_fragment* ts_out,
768
                      std::optional<string_fragment>* level_out)
769
{
770
    int curr_fmt = -1;
10,380✔
771
    const char* retval = nullptr;
10,380✔
772
    bool done = false;
10,380✔
773
    int pat_index = this->last_pattern_index();
10,380✔
774

775
    while (!done && next_format(fmt, curr_fmt, pat_index)) {
133,461✔
776
        thread_local auto md = lnav::pcre2pp::match_data::unitialized();
123,081✔
777

778
        auto match_res = fmt[curr_fmt]
123,081✔
779
                             .pcre->capture_from(line)
123,081✔
780
                             .into(md)
123,081✔
781
                             .matches(PCRE2_NO_UTF_CHECK)
246,162✔
782
                             .ignore_error();
123,081✔
783
        if (!match_res) {
123,081✔
784
            retval = nullptr;
109,085✔
785
        } else {
786
            auto ts = md[fmt[curr_fmt].pf_timestamp_index];
13,996✔
787

788
            retval = this->lf_date_time.scan(
13,996✔
789
                ts->data(), ts->length(), nullptr, tm_out, *tv_out);
13,996✔
790

791
            if (retval == nullptr) {
13,996✔
792
                auto ls = this->lf_date_time.unlock();
12,507✔
793
                retval = this->lf_date_time.scan(
12,507✔
794
                    ts->data(), ts->length(), nullptr, tm_out, *tv_out);
12,507✔
795
                if (retval != nullptr) {
12,507✔
UNCOV
796
                    auto old_flags
×
UNCOV
797
                        = this->lf_timestamp_flags & DATE_TIME_SET_FLAGS;
×
UNCOV
798
                    auto new_flags = tm_out->et_flags & DATE_TIME_SET_FLAGS;
×
799

800
                    // It is unlikely a valid timestamp would lose much
801
                    // precision.
UNCOV
802
                    if (new_flags != old_flags) {
×
UNCOV
803
                        retval = nullptr;
×
804
                    }
805
                }
806
                if (retval == nullptr) {
12,507✔
807
                    this->lf_date_time.relock(ls);
12,507✔
808
                } else {
UNCOV
809
                    log_debug(
×
810
                        "%d: changed time format to '%s' due to %.*s",
811
                        line_number,
812
                        PTIMEC_FORMAT_STR[this->lf_date_time.dts_fmt_lock],
813
                        ts->length(),
814
                        ts->data());
815
                }
816
            }
817

818
            if (retval) {
13,996✔
819
                *ts_out = ts.value();
1,489✔
820
                *level_out = md[2];
1,489✔
821
                if (curr_fmt != pat_index) {
1,489✔
822
                    uint32_t lock_line;
823

824
                    if (this->lf_pattern_locks.empty()) {
1,387✔
825
                        lock_line = 0;
1,387✔
826
                    } else {
UNCOV
827
                        lock_line = line_number;
×
828
                    }
829

830
                    this->lf_pattern_locks.emplace_back(lock_line, curr_fmt);
1,387✔
831
                }
832
                this->lf_timestamp_flags = tm_out->et_flags;
1,489✔
833
                done = true;
1,489✔
834
            }
835
        }
836
    }
837

838
    return retval;
10,380✔
839
}
840

841
void
842
log_format::annotate(logfile* lf,
35,232✔
843
                     uint64_t line_number,
844
                     string_attrs_t& sa,
845
                     logline_value_vector& values,
846
                     bool annotate_module) const
847
{
848
    if (lf != nullptr && !values.lvv_opid_value) {
35,232✔
849
        const auto& bm = lf->get_bookmark_metadata();
31,008✔
850
        auto bm_iter = bm.find(line_number);
31,008✔
851
        if (bm_iter != bm.end() && !bm_iter->second.bm_opid.empty()) {
31,008✔
852
            values.lvv_opid_value = bm_iter->second.bm_opid;
6✔
853
            values.lvv_opid_provenance
854
                = logline_value_vector::opid_provenance::user;
6✔
855
        }
856
    }
857
}
35,232✔
858

859
void
860
log_format::check_for_new_year(std::vector<logline>& dst,
1,418✔
861
                               exttm etm,
862
                               struct timeval log_tv)
863
{
864
    if (dst.empty()) {
1,418✔
865
        return;
201✔
866
    }
867

868
    time_t diff
869
        = dst.back().get_time<std::chrono::seconds>().count() - log_tv.tv_sec;
1,217✔
870
    int off_year = 0, off_month = 0, off_day = 0, off_hour = 0;
1,217✔
871
    bool do_change = true;
1,217✔
872

873
    if (diff <= 0) {
1,217✔
874
        return;
1,158✔
875
    }
876
    if ((etm.et_flags & ETF_MONTH_SET) && diff >= (24 * 60 * 60)) {
59✔
877
        off_year = 1;
10✔
878
    } else if (diff >= (24 * 60 * 60)) {
49✔
879
        off_month = 1;
2✔
880
    } else if (!(etm.et_flags & ETF_DAY_SET) && (diff >= (60 * 60))) {
47✔
881
        off_day = 1;
1✔
882
    } else if (!(etm.et_flags & ETF_HOUR_SET) && (diff >= 60)) {
46✔
883
        off_hour = 1;
4✔
884
    } else {
885
        do_change = false;
42✔
886
    }
887

888
    if (!do_change) {
59✔
889
        return;
42✔
890
    }
891
    log_debug("%d:detected time rollover; offsets=%d %d %d %d",
17✔
892
              dst.size(),
893
              off_year,
894
              off_month,
895
              off_day,
896
              off_hour);
897
    for (auto& ll : dst) {
66✔
898
        time_t ot = ll.get_time<std::chrono::seconds>().count();
49✔
899
        struct tm otm;
900

901
        gmtime_r(&ot, &otm);
49✔
902
        otm.tm_yday = -1;
49✔
903
        if (otm.tm_year < off_year) {
49✔
UNCOV
904
            otm.tm_year = 0;
×
905
        } else {
906
            otm.tm_year -= off_year;
49✔
907
        }
908
        otm.tm_mon -= off_month;
49✔
909
        if (otm.tm_mon < 0) {
49✔
910
            otm.tm_mon += 12;
2✔
911
        }
912
        auto new_time = tm2sec(&otm);
49✔
913
        if (new_time == -1) {
49✔
UNCOV
914
            continue;
×
915
        }
916
        new_time -= (off_day * 24 * 60 * 60) + (off_hour * 60 * 60);
49✔
917
        auto old_sub = ll.get_subsecond_time<std::chrono::microseconds>();
49✔
918
        ll.set_time(std::chrono::seconds{new_time});
49✔
919
        ll.set_subsecond_time(old_sub);
49✔
920
    }
921
}
922

923
/*
924
 * XXX This needs some cleanup.
925
 */
926
struct json_log_userdata {
927
    json_log_userdata(shared_buffer_ref& sbr, scan_batch_context* sbc)
5,505✔
928
        : jlu_shared_buffer(sbr), jlu_batch_context(sbc)
5,505✔
929
    {
930
    }
5,505✔
931

932
    const external_log_format::value_def* get_field_def(
90,077✔
933
        yajlpp_parse_context* ypc)
934
    {
935
        const auto field_frag = ypc->get_path_as_string_fragment();
90,077✔
936
        auto* format = this->jlu_format;
90,077✔
937

938
        if (this->jlu_read_order_index < format->elf_value_def_read_order.size()
90,077✔
939
            && format->elf_value_def_read_order[this->jlu_read_order_index]
122,989✔
940
                    .first
941
                == field_frag)
32,912✔
942
        {
943
            return format
944
                ->elf_value_def_read_order[this->jlu_read_order_index++]
31,618✔
945
                .second;
31,618✔
946
        }
947

948
        format->elf_value_def_read_order.resize(this->jlu_read_order_index);
58,459✔
949
        auto vd_iter = format->elf_value_def_frag_map.find(field_frag);
58,459✔
950
        if (vd_iter != format->elf_value_def_frag_map.end()) {
58,459✔
951
            format->elf_value_def_read_order.emplace_back(vd_iter->first,
48,484✔
952
                                                          vd_iter->second);
48,484✔
953
            this->jlu_read_order_index += 1;
48,484✔
954
            return vd_iter->second;
48,484✔
955
        }
956

957
        auto owned_frag = field_frag.to_owned(format->elf_allocator);
9,975✔
958
        format->elf_value_def_frag_map[owned_frag] = nullptr;
9,975✔
959
        format->elf_value_def_read_order.emplace_back(owned_frag, nullptr);
9,975✔
960
        this->jlu_read_order_index += 1;
9,975✔
961
        return nullptr;
9,975✔
962
    }
963

964
    void add_sub_lines_for(const external_log_format::value_def* vd,
64,685✔
965
                           bool top_level,
966
                           std::optional<double> val,
967
                           const unsigned char* str,
968
                           ssize_t len,
969
                           yajl_string_props_t* props)
970
    {
971
        auto res = this->jlu_format->value_line_count(
64,685✔
972
            vd, top_level, val, str, len, props);
973
        this->jlu_has_ansi |= res.vlcr_has_ansi;
64,685✔
974
        if (!res.vlcr_valid_utf) {
64,685✔
UNCOV
975
            this->jlu_valid_utf = false;
×
976
        }
977
        this->jlu_sub_line_count += res.vlcr_count;
64,685✔
978
        this->jlu_quality += res.vlcr_line_format_count;
64,685✔
979
        if (res.vlcr_line_format_index) {
64,685✔
980
            this->jlu_format_hits[res.vlcr_line_format_index.value()] = true;
1,934✔
981
        }
982
    }
64,685✔
983

984
    external_log_format* jlu_format{nullptr};
985
    const logline* jlu_line{nullptr};
986
    logline* jlu_base_line{nullptr};
987
    int jlu_sub_line_count{1};
988
    bool jlu_has_ansi{false};
989
    bool jlu_valid_utf{true};
990
    yajl_handle jlu_handle{nullptr};
991
    const char* jlu_line_value{nullptr};
992
    size_t jlu_line_size{0};
993
    size_t jlu_sub_start{0};
994
    uint32_t jlu_quality{0};
995
    uint32_t jlu_strikes{0};
996
    std::vector<bool> jlu_format_hits;
997
    shared_buffer_ref& jlu_shared_buffer;
998
    scan_batch_context* jlu_batch_context;
999
    std::optional<string_fragment> jlu_opid_frag;
1000
    std::optional<std::string> jlu_subid;
1001
    exttm jlu_exttm;
1002
    size_t jlu_read_order_index{0};
1003
    subline_options jlu_subline_opts;
1004
};
1005

1006
static int read_json_field(yajlpp_parse_context* ypc,
1007
                           const unsigned char* str,
1008
                           size_t len,
1009
                           yajl_string_props_t*);
1010

1011
static int
1012
read_json_null(yajlpp_parse_context* ypc)
940✔
1013
{
1014
    json_log_userdata* jlu = (json_log_userdata*) ypc->ypc_userdata;
940✔
1015
    const auto* vd = jlu->get_field_def(ypc);
940✔
1016

1017
    jlu->add_sub_lines_for(
1,880✔
1018
        vd, ypc->is_level(1), std::nullopt, nullptr, -1, nullptr);
940✔
1019

1020
    return 1;
940✔
1021
}
1022

1023
static int
1024
read_json_bool(yajlpp_parse_context* ypc, int val)
4,510✔
1025
{
1026
    json_log_userdata* jlu = (json_log_userdata*) ypc->ypc_userdata;
4,510✔
1027
    const auto* vd = jlu->get_field_def(ypc);
4,510✔
1028

1029
    jlu->add_sub_lines_for(
9,020✔
1030
        vd, ypc->is_level(1), std::nullopt, nullptr, -1, nullptr);
4,510✔
1031

1032
    return 1;
4,510✔
1033
}
1034

1035
static int
1036
read_json_number(yajlpp_parse_context* ypc,
7,828✔
1037
                 const char* numberVal,
1038
                 size_t numberLen)
1039
{
1040
    auto* jlu = (json_log_userdata*) ypc->ypc_userdata;
7,828✔
1041
    auto number_frag = string_fragment::from_bytes(numberVal, numberLen);
7,828✔
1042
    std::optional<double> val;
7,828✔
1043

1044
    intern_string_t field_name;
7,828✔
1045
    const auto* vd = jlu->get_field_def(ypc);
7,828✔
1046
    if (vd != nullptr) {
7,828✔
1047
        field_name = vd->vd_meta.lvm_name;
415✔
1048
    }
1049

1050
    if (field_name.empty()) {
7,828✔
1051
    } else if (jlu->jlu_format->lf_timestamp_field == field_name) {
415✔
1052
        long long divisor = jlu->jlu_format->elf_timestamp_divisor;
23✔
1053
        auto scan_res = scn::scan_value<double>(number_frag.to_string_view());
23✔
1054
        if (!scan_res) {
23✔
UNCOV
1055
            log_error("invalid number %.*s", numberLen, numberVal);
×
UNCOV
1056
            return 0;
×
1057
        }
1058
        auto ts_val = scan_res.value().value();
23✔
1059
        timeval tv;
1060
        tv.tv_sec = ts_val / divisor;
23✔
1061
        tv.tv_usec = fmod(ts_val, divisor) * (1000000.0 / divisor);
23✔
1062
        jlu->jlu_format->lf_date_time.to_localtime(tv.tv_sec, jlu->jlu_exttm);
23✔
1063
        tv.tv_sec = tm2sec(&jlu->jlu_exttm.et_tm);
23✔
1064
        jlu->jlu_exttm.et_gmtoff
1065
            = jlu->jlu_format->lf_date_time.dts_local_offset_cache;
23✔
1066
        jlu->jlu_exttm.et_flags
23✔
1067
            |= ETF_MACHINE_ORIENTED | ETF_SUB_NOT_IN_FORMAT | ETF_ZONE_SET;
23✔
1068
        if (divisor == 1000) {
23✔
1069
            jlu->jlu_exttm.et_flags |= ETF_MILLIS_SET;
17✔
1070
        } else {
1071
            jlu->jlu_exttm.et_flags |= ETF_MICROS_SET;
6✔
1072
        }
1073
        jlu->jlu_exttm.et_nsec = tv.tv_usec * 1000;
23✔
1074
        jlu->jlu_base_line->set_time(tv);
23✔
1075
    } else if (jlu->jlu_format->lf_subsecond_field == field_name) {
392✔
1076
        auto scan_res = scn::scan_value<double>(number_frag.to_string_view());
3✔
1077
        if (!scan_res) {
3✔
UNCOV
1078
            log_error("invalid number %.*s", numberLen, numberVal);
×
UNCOV
1079
            return 0;
×
1080
        }
1081
        auto ts_val = scan_res.value().value();
3✔
1082

1083
        uint64_t millis = 0;
3✔
1084
        jlu->jlu_exttm.et_flags &= ~(ETF_MICROS_SET | ETF_MILLIS_SET);
3✔
1085
        switch (jlu->jlu_format->lf_subsecond_unit.value()) {
3✔
UNCOV
1086
            case log_format::subsecond_unit::milli:
×
UNCOV
1087
                millis = ts_val;
×
UNCOV
1088
                jlu->jlu_exttm.et_nsec = ts_val * 1000000;
×
UNCOV
1089
                jlu->jlu_exttm.et_flags |= ETF_MILLIS_SET;
×
UNCOV
1090
                break;
×
UNCOV
1091
            case log_format::subsecond_unit::micro:
×
1092
                millis = std::chrono::duration_cast<std::chrono::milliseconds>(
×
1093
                             std::chrono::microseconds((int64_t) ts_val))
×
1094
                             .count();
×
1095
                jlu->jlu_exttm.et_nsec = ts_val * 1000;
×
1096
                jlu->jlu_exttm.et_flags |= ETF_MICROS_SET;
×
1097
                break;
×
1098
            case log_format::subsecond_unit::nano:
3✔
1099
                millis = std::chrono::duration_cast<std::chrono::milliseconds>(
3✔
1100
                             std::chrono::nanoseconds((int64_t) ts_val))
3✔
1101
                             .count();
3✔
1102
                jlu->jlu_exttm.et_nsec = ts_val;
3✔
1103
                jlu->jlu_exttm.et_flags |= ETF_NANOS_SET;
3✔
1104
                break;
3✔
1105
        }
1106
        jlu->jlu_exttm.et_flags |= ETF_SUB_NOT_IN_FORMAT;
3✔
1107
        jlu->jlu_base_line->set_subsecond_time(
3✔
1108
            std::chrono::milliseconds(millis));
1109
    } else if (jlu->jlu_format->elf_level_field == field_name) {
389✔
1110
        if (jlu->jlu_format->elf_level_pairs.empty()) {
224✔
1111
            jlu->jlu_base_line->set_level(jlu->jlu_format->convert_level(
128✔
1112
                number_frag, jlu->jlu_batch_context));
1113
        } else {
1114
            auto scan_res
1115
                = scn::scan_int<int64_t>(number_frag.to_string_view());
96✔
1116
            if (!scan_res) {
96✔
UNCOV
1117
                log_error("invalid number %.*s", numberLen, numberVal);
×
UNCOV
1118
                return 0;
×
1119
            }
1120
            auto level_int = scan_res.value().value();
96✔
1121

1122
            for (const auto& pair : jlu->jlu_format->elf_level_pairs) {
335✔
1123
                if (pair.first == level_int) {
301✔
1124
                    jlu->jlu_base_line->set_level(pair.second);
62✔
1125
                    break;
62✔
1126
                }
1127
            }
1128
        }
1129
    } else if (vd != nullptr) {
165✔
1130
        if ((vd->vd_meta.lvm_kind == value_kind_t::VALUE_INTEGER
165✔
1131
             || vd->vd_meta.lvm_kind == value_kind_t::VALUE_FLOAT)
6✔
1132
            && !vd->vd_meta.lvm_foreign_key && !vd->vd_meta.lvm_identifier)
159✔
1133
        {
1134
            auto scan_res
1135
                = scn::scan_value<double>(number_frag.to_string_view());
103✔
1136
            if (!scan_res) {
103✔
UNCOV
1137
                log_error("invalid number %.*s", numberLen, numberVal);
×
UNCOV
1138
                return 0;
×
1139
            }
1140
            val = scan_res.value().value();
103✔
1141
        }
1142
    }
1143

1144
    jlu->add_sub_lines_for(vd,
7,828✔
1145
                           ypc->is_level(1),
7,828✔
1146
                           val,
1147
                           (const unsigned char*) numberVal,
1148
                           numberLen,
1149
                           nullptr);
1150

1151
    return 1;
7,828✔
1152
}
1153

1154
static int
1155
json_array_start(void* ctx)
16,900✔
1156
{
1157
    auto* ypc = (yajlpp_parse_context*) ctx;
16,900✔
1158
    auto* jlu = (json_log_userdata*) ypc->ypc_userdata;
16,900✔
1159

1160
    if (ypc->ypc_path_index_stack.size() == 2) {
16,900✔
1161
        const intern_string_t field_name = ypc->get_path_fragment_i(0);
4,714✔
1162

1163
        external_log_format::value_def* vd = nullptr;
4,714✔
1164
        auto iter = jlu->jlu_format->elf_value_defs.find(field_name);
4,714✔
1165
        if (iter != jlu->jlu_format->elf_value_defs.end()) {
4,714✔
1166
            vd = iter->second.get();
1,201✔
1167
        }
1168
        jlu->add_sub_lines_for(vd, true, std::nullopt, nullptr, -1, nullptr);
4,714✔
1169
        jlu->jlu_sub_start = yajl_get_bytes_consumed(jlu->jlu_handle) - 1;
4,714✔
1170
    }
1171

1172
    return 1;
16,900✔
1173
}
1174

1175
static int
1176
json_array_end(void* ctx)
4,890✔
1177
{
1178
    yajlpp_parse_context* ypc = (yajlpp_parse_context*) ctx;
4,890✔
1179
    json_log_userdata* jlu = (json_log_userdata*) ypc->ypc_userdata;
4,890✔
1180

1181
    if (ypc->ypc_path_index_stack.size() == 1) {
4,890✔
1182
        const intern_string_t field_name = ypc->get_path_fragment_i(0);
1,265✔
1183
        size_t sub_end = yajl_get_bytes_consumed(jlu->jlu_handle);
1,265✔
1184
        jlu->jlu_format->jlf_line_values.lvv_values.emplace_back(
1,265✔
1185
            jlu->jlu_format->get_value_meta(field_name,
2,530✔
1186
                                            value_kind_t::VALUE_JSON),
1187
            string_fragment::from_byte_range(jlu->jlu_shared_buffer.get_data(),
2,530✔
1188
                                             jlu->jlu_sub_start,
1189
                                             sub_end));
1190
    }
1191

1192
    return 1;
4,890✔
1193
}
1194

1195
static const json_path_container json_log_handlers = {
1196
    yajlpp::pattern_property_handler("\\w+")
1197
        .add_cb(read_json_null)
1198
        .add_cb(read_json_bool)
1199
        .add_cb(read_json_number)
1200
        .add_cb(read_json_field),
1201
};
1202

1203
static int rewrite_json_field(yajlpp_parse_context* ypc,
1204
                              const unsigned char* str,
1205
                              size_t len,
1206
                              yajl_string_props_t*);
1207

1208
static int
1209
rewrite_json_null(yajlpp_parse_context* ypc)
593✔
1210
{
1211
    auto jlu = (json_log_userdata*) ypc->ypc_userdata;
593✔
1212
    const auto* vd = jlu->get_field_def(ypc);
593✔
1213

1214
    if (!ypc->is_level(1) && vd == nullptr) {
593✔
1215
        return 1;
593✔
1216
    }
UNCOV
1217
    jlu->jlu_format->jlf_line_values.lvv_values.emplace_back(
×
UNCOV
1218
        jlu->jlu_format->get_value_meta(ypc, vd, value_kind_t::VALUE_NULL));
×
1219

UNCOV
1220
    return 1;
×
1221
}
1222

1223
static int
1224
rewrite_json_bool(yajlpp_parse_context* ypc, int val)
2,622✔
1225
{
1226
    auto* jlu = (json_log_userdata*) ypc->ypc_userdata;
2,622✔
1227
    const auto* vd = jlu->get_field_def(ypc);
2,622✔
1228

1229
    if (!ypc->is_level(1) && vd == nullptr) {
2,622✔
1230
        return 1;
2,360✔
1231
    }
1232
    jlu->jlu_format->jlf_line_values.lvv_values.emplace_back(
262✔
1233
        jlu->jlu_format->get_value_meta(ypc, vd, value_kind_t::VALUE_BOOLEAN),
262✔
1234
        (bool) val);
262✔
1235
    return 1;
262✔
1236
}
1237

1238
static int
1239
rewrite_json_int(yajlpp_parse_context* ypc, long long val)
3,391✔
1240
{
1241
    auto* jlu = (json_log_userdata*) ypc->ypc_userdata;
3,391✔
1242
    const auto* vd = jlu->get_field_def(ypc);
3,391✔
1243

1244
    if (vd != nullptr) {
3,391✔
1245
        const intern_string_t field_name = vd->vd_meta.lvm_name;
472✔
1246
        if (jlu->jlu_format->lf_timestamp_field == field_name) {
472✔
1247
            long long divisor = jlu->jlu_format->elf_timestamp_divisor;
25✔
1248
            timeval tv;
1249

1250
            tv.tv_sec = val / divisor;
25✔
1251
            tv.tv_usec = fmod(val, divisor) * (1000000.0 / divisor);
25✔
1252
            jlu->jlu_format->lf_date_time.to_localtime(tv.tv_sec,
25✔
1253
                                                       jlu->jlu_exttm);
25✔
1254
            jlu->jlu_exttm.et_gmtoff
1255
                = jlu->jlu_format->lf_date_time.dts_local_offset_cache;
25✔
1256
            jlu->jlu_exttm.et_flags |= ETF_MACHINE_ORIENTED
25✔
1257
                | ETF_SUB_NOT_IN_FORMAT | ETF_ZONE_SET | ETF_Z_FOR_UTC;
1258
            if (divisor == 1) {
25✔
1259
                jlu->jlu_exttm.et_flags |= ETF_MICROS_SET;
4✔
1260
            } else {
1261
                jlu->jlu_exttm.et_flags |= ETF_MILLIS_SET;
21✔
1262
            }
1263
            jlu->jlu_exttm.et_nsec = tv.tv_usec * 1000;
25✔
1264
        } else if (jlu->jlu_format->lf_subsecond_field == field_name) {
447✔
1265
            jlu->jlu_exttm.et_flags &= ~(ETF_MICROS_SET | ETF_MILLIS_SET);
4✔
1266
            switch (jlu->jlu_format->lf_subsecond_unit.value()) {
4✔
UNCOV
1267
                case log_format::subsecond_unit::milli:
×
UNCOV
1268
                    jlu->jlu_exttm.et_nsec = val * 1000000;
×
UNCOV
1269
                    jlu->jlu_exttm.et_flags |= ETF_MILLIS_SET;
×
UNCOV
1270
                    break;
×
UNCOV
1271
                case log_format::subsecond_unit::micro:
×
UNCOV
1272
                    jlu->jlu_exttm.et_nsec = val * 1000;
×
UNCOV
1273
                    jlu->jlu_exttm.et_flags |= ETF_MICROS_SET;
×
UNCOV
1274
                    break;
×
1275
                case log_format::subsecond_unit::nano:
4✔
1276
                    jlu->jlu_exttm.et_nsec = val;
4✔
1277
                    jlu->jlu_exttm.et_flags |= ETF_NANOS_SET;
4✔
1278
                    break;
4✔
1279
            }
1280
            jlu->jlu_exttm.et_flags |= ETF_SUB_NOT_IN_FORMAT;
4✔
1281
        }
1282
    }
1283

1284
    if (!ypc->is_level(1) && vd == nullptr) {
3,391✔
1285
        return 1;
2,913✔
1286
    }
1287
    jlu->jlu_format->jlf_line_values.lvv_values.emplace_back(
478✔
1288
        jlu->jlu_format->get_value_meta(ypc, vd, value_kind_t::VALUE_INTEGER),
478✔
1289
        (int64_t) val);
478✔
1290
    return 1;
478✔
1291
}
1292

1293
static int
1294
rewrite_json_double(yajlpp_parse_context* ypc, double val)
12✔
1295
{
1296
    auto* jlu = (json_log_userdata*) ypc->ypc_userdata;
12✔
1297
    const auto* vd = jlu->get_field_def(ypc);
12✔
1298

1299
    if (vd != nullptr) {
12✔
1300
        const intern_string_t field_name = vd->vd_meta.lvm_name;
12✔
1301
        if (jlu->jlu_format->lf_timestamp_field == field_name) {
12✔
1302
            long long divisor = jlu->jlu_format->elf_timestamp_divisor;
12✔
1303
            timeval tv;
1304

1305
            tv.tv_sec = val / divisor;
12✔
1306
            tv.tv_usec = fmod(val, divisor) * (1000000.0 / divisor);
12✔
1307
            jlu->jlu_format->lf_date_time.to_localtime(tv.tv_sec,
12✔
1308
                                                       jlu->jlu_exttm);
12✔
1309
            jlu->jlu_exttm.et_gmtoff
1310
                = jlu->jlu_format->lf_date_time.dts_local_offset_cache;
12✔
1311
            jlu->jlu_exttm.et_flags |= ETF_MACHINE_ORIENTED
12✔
1312
                | ETF_SUB_NOT_IN_FORMAT | ETF_ZONE_SET | ETF_Z_FOR_UTC;
1313
            if (divisor == 1) {
12✔
UNCOV
1314
                jlu->jlu_exttm.et_flags |= ETF_MICROS_SET;
×
1315
            } else {
1316
                jlu->jlu_exttm.et_flags |= ETF_MILLIS_SET;
12✔
1317
            }
1318
            jlu->jlu_exttm.et_nsec = tv.tv_usec * 1000;
12✔
1319
        } else if (jlu->jlu_format->lf_subsecond_field == field_name) {
×
1320
            jlu->jlu_exttm.et_flags &= ~(ETF_MICROS_SET | ETF_MILLIS_SET);
×
1321
            switch (jlu->jlu_format->lf_subsecond_unit.value()) {
×
UNCOV
1322
                case log_format::subsecond_unit::milli:
×
1323
                    jlu->jlu_exttm.et_nsec = val * 1000000;
×
1324
                    jlu->jlu_exttm.et_flags |= ETF_MILLIS_SET;
×
UNCOV
1325
                    break;
×
1326
                case log_format::subsecond_unit::micro:
×
UNCOV
1327
                    jlu->jlu_exttm.et_nsec = val * 1000;
×
UNCOV
1328
                    jlu->jlu_exttm.et_flags |= ETF_MICROS_SET;
×
UNCOV
1329
                    break;
×
UNCOV
1330
                case log_format::subsecond_unit::nano:
×
UNCOV
1331
                    jlu->jlu_exttm.et_nsec = val;
×
UNCOV
1332
                    jlu->jlu_exttm.et_flags |= ETF_NANOS_SET;
×
UNCOV
1333
                    break;
×
1334
            }
UNCOV
1335
            jlu->jlu_exttm.et_flags |= ETF_SUB_NOT_IN_FORMAT;
×
1336
        }
1337
    }
1338

1339
    if (!ypc->is_level(1) && vd == nullptr) {
12✔
UNCOV
1340
        return 1;
×
1341
    }
1342
    jlu->jlu_format->jlf_line_values.lvv_values.emplace_back(
12✔
1343
        jlu->jlu_format->get_value_meta(ypc, vd, value_kind_t::VALUE_FLOAT),
24✔
1344
        val);
1345

1346
    return 1;
12✔
1347
}
1348

1349
static const json_path_container json_log_rewrite_handlers = {
1350
    yajlpp::pattern_property_handler("\\w+")
1351
        .add_cb(rewrite_json_null)
1352
        .add_cb(rewrite_json_bool)
1353
        .add_cb(rewrite_json_int)
1354
        .add_cb(rewrite_json_double)
1355
        .add_cb(rewrite_json_field),
1356
};
1357

1358
bool
1359
external_log_format::scan_for_partial(shared_buffer_ref& sbr,
1✔
1360
                                      size_t& len_out) const
1361
{
1362
    if (this->elf_type != elf_type_t::ELF_TYPE_TEXT) {
1✔
UNCOV
1363
        return false;
×
1364
    }
1365

1366
    const auto& pat = this->elf_pattern_order[this->last_pattern_index()];
1✔
1367
    if (!this->lf_multiline) {
1✔
1368
        len_out = pat->p_pcre.pp_value->match_partial(sbr.to_string_fragment());
1✔
1369
        return true;
1✔
1370
    }
1371

UNCOV
1372
    if (pat->p_timestamp_end == -1 || pat->p_timestamp_end > (int) sbr.length())
×
1373
    {
UNCOV
1374
        len_out = 0;
×
UNCOV
1375
        return false;
×
1376
    }
1377

UNCOV
1378
    len_out = pat->p_pcre.pp_value->match_partial(sbr.to_string_fragment());
×
UNCOV
1379
    return (int) len_out > pat->p_timestamp_end;
×
1380
}
1381

1382
std::vector<lnav::console::snippet>
1383
external_log_format::get_snippets() const
17✔
1384
{
1385
    std::vector<lnav::console::snippet> retval;
17✔
1386

1387
    for (const auto& src_pair : this->elf_format_sources) {
34✔
1388
        retval.emplace_back(lnav::console::snippet::from(src_pair.first, "")
34✔
1389
                                .with_line(src_pair.second));
17✔
1390
    }
1391

1392
    return retval;
17✔
UNCOV
1393
}
×
1394

1395
log_format::scan_result_t
1396
external_log_format::scan_json(std::vector<logline>& dst,
104,409✔
1397
                               const line_info& li,
1398
                               shared_buffer_ref& sbr,
1399
                               scan_batch_context& sbc)
1400
{
1401
    logline ll(
1402
        li.li_file_range.fr_offset, std::chrono::microseconds{0}, LEVEL_INFO);
104,409✔
1403
    auto line_frag = sbr.to_string_fragment();
104,409✔
1404

1405
    if (!line_frag.startswith("{")) {
104,409✔
1406
        if (!this->lf_specialized) {
100,475✔
1407
            return scan_no_match{"line is not a JSON object"};
100,470✔
1408
        }
1409

1410
        ll.set_time(dst.back().get_time<std::chrono::microseconds>());
5✔
1411
        ll.set_level(LEVEL_INVALID);
5✔
1412
        dst.emplace_back(ll);
5✔
1413
        return scan_match{0};
5✔
1414
    }
1415

1416
    auto& ypc = *(this->jlf_parse_context);
3,934✔
1417
    yajl_handle handle = this->jlf_yajl_handle.get();
3,934✔
1418
    json_log_userdata jlu(sbr, &sbc);
3,934✔
1419

1420
    if (li.li_partial) {
3,934✔
UNCOV
1421
        log_debug("skipping partial line at offset %d",
×
1422
                  li.li_file_range.fr_offset);
UNCOV
1423
        if (this->lf_specialized) {
×
UNCOV
1424
            if (!dst.empty()) {
×
UNCOV
1425
                ll.set_time(dst.back().get_time<std::chrono::microseconds>());
×
1426
            }
UNCOV
1427
            ll.set_level(LEVEL_INVALID);
×
UNCOV
1428
            dst.emplace_back(ll);
×
1429
        }
UNCOV
1430
        return scan_incomplete{};
×
1431
    }
1432

1433
    const auto* line_data = (const unsigned char*) sbr.get_data();
3,934✔
1434

1435
    this->lf_desc_captures.clear();
3,934✔
1436
    this->lf_desc_allocator.reset();
3,934✔
1437

1438
    yajl_reset(handle);
3,934✔
1439
    ypc.set_static_handler(json_log_handlers.jpc_children[0]);
3,934✔
1440
    ypc.ypc_userdata = &jlu;
3,934✔
1441
    ypc.ypc_ignore_unused = true;
3,934✔
1442
    ypc.ypc_alt_callbacks.yajl_start_array = json_array_start;
3,934✔
1443
    ypc.ypc_alt_callbacks.yajl_start_map = json_array_start;
3,934✔
1444
    ypc.ypc_alt_callbacks.yajl_end_array = nullptr;
3,934✔
1445
    ypc.ypc_alt_callbacks.yajl_end_map = nullptr;
3,934✔
1446
    jlu.jlu_format = this;
3,934✔
1447
    jlu.jlu_base_line = &ll;
3,934✔
1448
    jlu.jlu_line_value = sbr.get_data();
3,934✔
1449
    jlu.jlu_line_size = sbr.length();
3,934✔
1450
    jlu.jlu_handle = handle;
3,934✔
1451
    jlu.jlu_format_hits.resize(this->jlf_line_format.size());
3,934✔
1452
    if (yajl_parse(handle, line_data, sbr.length()) == yajl_status_ok
3,934✔
1453
        && yajl_complete_parse(handle) == yajl_status_ok)
3,934✔
1454
    {
1455
        if (ll.get_time<std::chrono::microseconds>().count() == 0) {
3,791✔
1456
            if (this->lf_specialized) {
3,143✔
UNCOV
1457
                if (!dst.empty()) {
×
UNCOV
1458
                    ll.set_time(
×
UNCOV
1459
                        dst.back().get_time<std::chrono::microseconds>());
×
1460
                }
UNCOV
1461
                ll.set_ignore(true);
×
UNCOV
1462
                dst.emplace_back(ll);
×
UNCOV
1463
                return scan_match{0};
×
1464
            }
1465

1466
            return scan_no_match{
3,143✔
1467
                "JSON message does not have expected timestamp property"};
3,143✔
1468
        }
1469

1470
        if (jlu.jlu_opid_frag) {
648✔
1471
            this->jlf_line_values.lvv_opid_value
1472
                = jlu.jlu_opid_frag->to_string();
423✔
1473
            this->jlf_line_values.lvv_opid_provenance
1474
                = logline_value_vector::opid_provenance::file;
423✔
1475
            auto opid_iter = sbc.sbc_opids.insert_op(
846✔
1476
                sbc.sbc_allocator, jlu.jlu_opid_frag.value(), ll.get_timeval());
423✔
1477
            opid_iter->second.otr_level_stats.update_msg_count(
423✔
1478
                ll.get_msg_level());
1479

1480
            if (jlu.jlu_subid) {
423✔
1481
                auto subid_frag
UNCOV
1482
                    = string_fragment::from_str(jlu.jlu_subid.value());
×
1483

1484
                auto* ostr = sbc.sbc_opids.sub_op_in_use(sbc.sbc_allocator,
×
1485
                                                         opid_iter,
1486
                                                         subid_frag,
1487
                                                         ll.get_timeval(),
×
1488
                                                         ll.get_msg_level());
UNCOV
1489
                if (ostr != nullptr && ostr->ostr_description.empty()) {
×
UNCOV
1490
                    log_op_description sub_desc;
×
UNCOV
1491
                    this->update_op_description(*this->lf_subid_description_def,
×
1492
                                                sub_desc);
UNCOV
1493
                    if (!sub_desc.lod_elements.empty()) {
×
UNCOV
1494
                        auto& sub_desc_def = this->lf_subid_description_def->at(
×
UNCOV
1495
                            sub_desc.lod_id.value());
×
1496
                        ostr->ostr_description
UNCOV
1497
                            = sub_desc_def.to_string(sub_desc.lod_elements);
×
1498
                    }
1499
                }
1500
            }
1501

1502
            auto& otr = opid_iter->second;
423✔
1503
            this->update_op_description(*this->lf_opid_description_def,
423✔
1504
                                        otr.otr_description);
423✔
1505
        } else {
1506
            this->jlf_line_values.lvv_opid_value = std::nullopt;
225✔
1507
        }
1508

1509
        jlu.jlu_sub_line_count += this->jlf_line_format_init_count;
648✔
1510
        for (int lpc = 0; lpc < jlu.jlu_sub_line_count; lpc++) {
2,320✔
1511
            ll.set_sub_offset(lpc);
1,672✔
1512
            if (lpc > 0) {
1,672✔
1513
                ll.set_level(
1,024✔
1514
                    (log_level_t) (ll.get_level_and_flags() | LEVEL_CONTINUED));
1,024✔
1515
            }
1516
            ll.set_has_ansi(jlu.jlu_has_ansi);
1,672✔
1517
            ll.set_valid_utf(jlu.jlu_valid_utf);
1,672✔
1518
            dst.emplace_back(ll);
1,672✔
1519
        }
1520

1521
        if (!this->lf_specialized) {
648✔
1522
            static const intern_string_t ts_field
1523
                = intern_string::lookup("__timestamp__", -1);
336✔
1524
            static const intern_string_t level_field
1525
                = intern_string::lookup("__level__");
412✔
1526
            for (const auto& [index, jfe] :
3,770✔
1527
                 lnav::itertools::enumerate(this->jlf_line_format))
4,106✔
1528
            {
1529
                if (jfe.jfe_type != json_log_field::VARIABLE
8,135✔
1530
                    || jfe.jfe_value.pp_value == ts_field
2,849✔
1531
                    || jfe.jfe_value.pp_value == level_field
2,561✔
1532
                    || jfe.jfe_default_value != "-")
6,283✔
1533
                {
1534
                    continue;
1,267✔
1535
                }
1536
                if (!jlu.jlu_format_hits[index]) {
2,167✔
1537
                    jlu.jlu_strikes += 1;
1,776✔
1538
                }
1539
            }
1540
        }
1541
    } else {
1542
        unsigned char* msg;
1543
        int line_count = 2;
143✔
1544

1545
        msg = yajl_get_error(
286✔
1546
            handle, 1, (const unsigned char*) sbr.get_data(), sbr.length());
143✔
1547
        if (msg != nullptr) {
143✔
1548
            auto msg_frag = string_fragment::from_c_str(msg);
143✔
1549
            log_debug("Unable to parse line at offset %d: %s",
143✔
1550
                      li.li_file_range.fr_offset,
1551
                      msg);
1552
            line_count = msg_frag.count('\n') + 1;
143✔
1553
            yajl_free_error(handle, msg);
143✔
1554
        }
1555
        if (!this->lf_specialized) {
143✔
1556
            return scan_no_match{"JSON parsing failed"};
140✔
1557
        }
1558
        for (int lpc = 0; lpc < line_count; lpc++) {
15✔
1559
            log_level_t level = LEVEL_INVALID;
12✔
1560

1561
            ll.set_time(dst.back().get_timeval());
12✔
1562
            if (lpc > 0) {
12✔
1563
                level = (log_level_t) (level | LEVEL_CONTINUED);
9✔
1564
            }
1565
            ll.set_level(level);
12✔
1566
            ll.set_sub_offset(lpc);
12✔
1567
            dst.emplace_back(ll);
12✔
1568
        }
1569
    }
1570

1571
    if (jlu.jlu_quality > 0) {
651✔
1572
        jlu.jlu_quality += 3000;
565✔
1573
    }
1574
    return scan_match{jlu.jlu_quality, jlu.jlu_strikes};
651✔
1575
}
3,934✔
1576

1577
log_format::scan_result_t
1578
external_log_format::scan(logfile& lf,
686,751✔
1579
                          std::vector<logline>& dst,
1580
                          const line_info& li,
1581
                          shared_buffer_ref& sbr,
1582
                          scan_batch_context& sbc)
1583
{
1584
    if (dst.empty()) {
686,751✔
1585
        auto file_options = lf.get_file_options();
30,041✔
1586

1587
        if (file_options) {
30,041✔
1588
            this->lf_date_time.dts_default_zone
1589
                = file_options->second.fo_default_zone.pp_value;
2,205✔
1590
        } else {
1591
            this->lf_date_time.dts_default_zone = nullptr;
27,836✔
1592
        }
1593
    }
30,041✔
1594

1595
    if (this->elf_type == elf_type_t::ELF_TYPE_JSON) {
686,751✔
1596
        return this->scan_json(dst, li, sbr, sbc);
104,407✔
1597
    }
1598

1599
    int curr_fmt = -1, orig_lock = this->last_pattern_index();
582,344✔
1600
    int pat_index = orig_lock;
582,344✔
1601
    auto line_sf = sbr.to_string_fragment();
582,344✔
1602
    thread_local auto md = lnav::pcre2pp::match_data::unitialized();
582,344✔
1603

1604
    while (::next_format(this->elf_pattern_order, curr_fmt, pat_index)) {
1,917,526✔
1605
        auto* fpat = this->elf_pattern_order[curr_fmt].get();
1,340,841✔
1606
        auto* pat = fpat->p_pcre.pp_value.get();
1,340,841✔
1607

1608
        if (fpat->p_module_format) {
1,340,841✔
1609
            continue;
1,335,182✔
1610
        }
1611

1612
        auto found_match
1613
            = pat->capture_from(line_sf).into(md).found_p(PCRE2_NO_UTF_CHECK);
1,311,042✔
1614
        if (!found_match) {
1,311,042✔
1615
            if (!this->lf_pattern_locks.empty() && pat_index != -1) {
1,305,380✔
1616
                curr_fmt = -1;
1,948✔
1617
                pat_index = -1;
1,948✔
1618
            }
1619
            continue;
1,305,380✔
1620
        }
1621

1622
        auto ts = md[fpat->p_timestamp_field_index];
5,662✔
1623
        auto level_cap = md[fpat->p_level_field_index];
5,662✔
1624
        auto opid_cap = md[fpat->p_opid_field_index];
5,662✔
1625
        auto body_cap = md[fpat->p_body_field_index];
5,662✔
1626
        const char* last;
1627
        exttm log_time_tm;
5,662✔
1628
        timeval log_tv;
1629
        uint8_t mod_index = 0;
5,662✔
1630
        uint16_t opid = 0;
5,662✔
1631
        char combined_datetime_buf[512];
1632

1633
        if (fpat->p_time_field_index != -1) {
5,662✔
UNCOV
1634
            auto time_cap = md[fpat->p_time_field_index];
×
UNCOV
1635
            if (ts && time_cap) {
×
UNCOV
1636
                auto ts_str_len = snprintf(combined_datetime_buf,
×
1637
                                           sizeof(combined_datetime_buf),
1638
                                           "%.*sT%.*s",
1639
                                           ts->length(),
1640
                                           ts->data(),
1641
                                           time_cap->length(),
1642
                                           time_cap->data());
UNCOV
1643
                ts = string_fragment::from_bytes(combined_datetime_buf,
×
UNCOV
1644
                                                 ts_str_len);
×
1645
            }
1646
        }
1647

1648
        auto level = this->convert_level(
5,662✔
1649
            level_cap.value_or(string_fragment::invalid()), &sbc);
5,662✔
1650

1651
        if (!ts) {
5,662✔
UNCOV
1652
            level = log_level_t::LEVEL_INVALID;
×
1653
        } else if ((last
5,662✔
1654
                    = this->lf_date_time.scan(ts->data(),
11,324✔
1655
                                              ts->length(),
5,662✔
1656
                                              this->get_timestamp_formats(),
1657
                                              &log_time_tm,
1658
                                              log_tv))
1659
                   == nullptr)
5,662✔
1660
        {
1661
            auto ls = this->lf_date_time.unlock();
13✔
1662
            if ((last = this->lf_date_time.scan(ts->data(),
26✔
1663
                                                ts->length(),
13✔
1664
                                                this->get_timestamp_formats(),
1665
                                                &log_time_tm,
1666
                                                log_tv))
1667
                == nullptr)
13✔
1668
            {
1669
                this->lf_date_time.relock(ls);
2✔
1670
                continue;
3✔
1671
            }
1672
            if (last != nullptr) {
11✔
1673
                auto old_flags = this->lf_timestamp_flags & DATE_TIME_SET_FLAGS;
11✔
1674
                auto new_flags = log_time_tm.et_flags & DATE_TIME_SET_FLAGS;
11✔
1675

1676
                // It is unlikely a valid timestamp would lose much
1677
                // precision.
1678
                if (new_flags != old_flags) {
11✔
1679
                    continue;
1✔
1680
                }
1681
            }
1682

1683
            log_debug("%s:%d: date-time re-locked to %d",
10✔
1684
                      lf.get_unique_path().c_str(),
1685
                      dst.size(),
1686
                      this->lf_date_time.dts_fmt_lock);
1687
        }
1688

1689
        this->lf_timestamp_flags = log_time_tm.et_flags;
5,659✔
1690

1691
        if (!(this->lf_timestamp_flags
11,318✔
1692
              & (ETF_MILLIS_SET | ETF_MICROS_SET | ETF_NANOS_SET))
5,659✔
1693
            && !dst.empty()
5,243✔
1694
            && dst.back().get_time<std::chrono::seconds>().count()
4,526✔
1695
                == log_tv.tv_sec
4,526✔
1696
            && dst.back()
14,323✔
1697
                    .get_subsecond_time<std::chrono::milliseconds>()
9,080✔
1698
                    .count()
3,421✔
1699
                != 0)
1700
        {
1701
            auto log_ms
UNCOV
1702
                = dst.back().get_subsecond_time<std::chrono::microseconds>();
×
1703

1704
            log_time_tm.et_nsec
UNCOV
1705
                = std::chrono::duration_cast<std::chrono::nanoseconds>(log_ms)
×
UNCOV
1706
                      .count();
×
1707
            log_tv.tv_usec
UNCOV
1708
                = std::chrono::duration_cast<std::chrono::microseconds>(log_ms)
×
UNCOV
1709
                      .count();
×
1710
        }
1711

1712
        if (!((log_time_tm.et_flags & ETF_DAY_SET)
5,659✔
1713
              && (log_time_tm.et_flags & ETF_MONTH_SET)
5,618✔
1714
              && (log_time_tm.et_flags & ETF_YEAR_SET)))
5,618✔
1715
        {
1716
            this->check_for_new_year(dst, log_time_tm, log_tv);
742✔
1717
        }
1718

1719
        if (opid_cap && !opid_cap->empty()) {
5,659✔
1720
            auto opid_iter = sbc.sbc_opids.insert_op(
9,454✔
1721
                sbc.sbc_allocator, opid_cap.value(), log_tv);
4,727✔
1722
            auto& otr = opid_iter->second;
4,727✔
1723

1724
            otr.otr_level_stats.update_msg_count(level);
4,727✔
1725
            if (fpat->p_subid_field_index != -1) {
4,727✔
1726
                auto subid_cap = md[fpat->p_subid_field_index];
49✔
1727
                if (subid_cap && !subid_cap->empty()) {
49✔
1728
                    auto* ostr = sbc.sbc_opids.sub_op_in_use(sbc.sbc_allocator,
147✔
1729
                                                             opid_iter,
1730
                                                             subid_cap.value(),
49✔
1731
                                                             log_tv,
1732
                                                             level);
1733
                    if (ostr != nullptr && ostr->ostr_description.empty()) {
49✔
1734
                        log_op_description sub_desc;
33✔
1735
                        this->update_op_description(
33✔
1736
                            *this->lf_subid_description_def,
33✔
1737
                            sub_desc,
1738
                            fpat,
1739
                            md);
1740
                        if (!sub_desc.lod_elements.empty()) {
33✔
1741
                            auto& sub_desc_def
1742
                                = this->lf_subid_description_def->at(
31✔
1743
                                    sub_desc.lod_id.value());
31✔
1744
                            ostr->ostr_description
1745
                                = sub_desc_def.to_string(sub_desc.lod_elements);
31✔
1746
                        }
1747
                    }
33✔
1748
                }
1749
            }
1750
            this->update_op_description(
4,727✔
1751
                *this->lf_opid_description_def, otr.otr_description, fpat, md);
4,727✔
1752
            opid = opid_cap->hash();
4,727✔
1753
        }
1754
        if (fpat->p_module_field_index != -1) {
5,659✔
1755
            auto mod_cap = md[fpat->p_module_field_index];
627✔
1756
            if (mod_cap && body_cap) {
627✔
1757
                intern_string_t mod_name
1758
                    = intern_string::lookup(mod_cap.value());
624✔
1759
                auto mod_iter = MODULE_FORMATS.find(mod_name);
624✔
1760

1761
                if (mod_iter == MODULE_FORMATS.end()) {
624✔
1762
                    mod_index = this->module_scan(body_cap.value(), mod_name);
159✔
1763
                    mod_iter = MODULE_FORMATS.find(mod_name);
159✔
1764
                } else if (mod_iter->second.mf_mod_format) {
465✔
1765
                    mod_index = mod_iter->second.mf_mod_format->lf_mod_index;
4✔
1766
                }
1767

1768
                if (mod_index && level_cap && body_cap) {
624✔
1769
                    auto mod_elf
1770
                        = std::dynamic_pointer_cast<external_log_format>(
1771
                            mod_iter->second.mf_mod_format);
45✔
1772

1773
                    if (mod_elf) {
45✔
1774
                        thread_local auto mod_md
1775
                            = lnav::pcre2pp::match_data::unitialized();
45✔
1776

1777
                        shared_buffer_ref body_ref;
45✔
1778

1779
                        body_cap->trim();
45✔
1780

1781
                        int mod_pat_index = mod_elf->last_pattern_index();
45✔
1782
                        auto& mod_pat
1783
                            = *mod_elf->elf_pattern_order[mod_pat_index];
45✔
1784
                        auto match_res = mod_pat.p_pcre.pp_value
45✔
1785
                                             ->capture_from(body_cap.value())
45✔
1786
                                             .into(mod_md)
45✔
1787
                                             .matches(PCRE2_NO_UTF_CHECK)
90✔
1788
                                             .ignore_error();
45✔
1789
                        if (match_res) {
45✔
1790
                            auto mod_level_cap
1791
                                = mod_md[mod_pat.p_level_field_index];
45✔
1792

1793
                            level = mod_elf->convert_level(
90✔
1794
                                mod_level_cap.value_or(
1795
                                    string_fragment::invalid()),
90✔
1796
                                &sbc);
1797
                        }
1798
                    }
45✔
1799
                }
45✔
1800
            }
1801
        }
1802

1803
        for (const auto& ivd : fpat->p_value_by_index) {
64,345✔
1804
            if (!ivd.ivd_value_def->vd_meta.lvm_values_index) {
58,686✔
1805
                continue;
10,652✔
1806
            }
1807

1808
            ssize_t cap_size = md.capture_size(ivd.ivd_index);
48,034✔
1809
            auto& lvs = this->lf_value_stats[ivd.ivd_value_def->vd_meta
48,034✔
1810
                                                 .lvm_values_index.value()];
48,034✔
1811

1812
            if (cap_size > lvs.lvs_width) {
48,034✔
1813
                lvs.lvs_width = cap_size;
3,556✔
1814
            }
1815
        }
1816

1817
        for (auto value_index : fpat->p_numeric_value_indexes) {
10,342✔
1818
            const indexed_value_def& ivd = fpat->p_value_by_index[value_index];
4,683✔
1819
            const value_def& vd = *ivd.ivd_value_def;
4,683✔
1820
            auto num_cap = md[ivd.ivd_index];
4,683✔
1821

1822
            if (num_cap && num_cap->is_valid()) {
4,683✔
1823
                const struct scaling_factor* scaling = nullptr;
4,683✔
1824

1825
                if (ivd.ivd_unit_field_index >= 0) {
4,683✔
1826
                    auto unit_cap = md[ivd.ivd_unit_field_index];
80✔
1827

1828
                    if (unit_cap && unit_cap->is_valid()) {
80✔
1829
                        intern_string_t unit_val
1830
                            = intern_string::lookup(unit_cap.value());
80✔
1831

1832
                        auto unit_iter = vd.vd_unit_scaling.find(unit_val);
80✔
1833
                        if (unit_iter != vd.vd_unit_scaling.end()) {
80✔
1834
                            const auto& sf = unit_iter->second;
80✔
1835

1836
                            scaling = &sf;
80✔
1837
                        }
1838
                    }
1839
                }
1840

1841
                std::optional<double> dvalue_opt;
4,683✔
1842
                switch (vd.vd_meta.lvm_kind) {
4,683✔
1843
                    case value_kind_t::VALUE_INTEGER: {
4,573✔
1844
                        auto scan_res
1845
                            = scn::scan_int<int64_t>(num_cap->to_string_view());
4,573✔
1846
                        if (scan_res) {
4,573✔
1847
                            dvalue_opt = scan_res->value();
4,573✔
1848
                        }
1849
                        break;
4,573✔
1850
                    }
1851
                    case value_kind_t::VALUE_FLOAT: {
110✔
1852
                        auto scan_res = scn::scan_value<double>(
1853
                            num_cap->to_string_view());
110✔
1854
                        if (scan_res) {
110✔
1855
                            dvalue_opt = scan_res->value();
110✔
1856
                        }
1857
                        break;
110✔
1858
                    }
UNCOV
1859
                    default:
×
UNCOV
1860
                        break;
×
1861
                }
1862
                if (dvalue_opt) {
4,683✔
1863
                    auto dvalue = dvalue_opt.value();
4,683✔
1864
                    if (scaling != nullptr) {
4,683✔
1865
                        scaling->scale(dvalue);
80✔
1866
                    }
1867
                    this->lf_value_stats[vd.vd_meta.lvm_values_index.value()]
4,683✔
1868
                        .add_value(dvalue);
4,683✔
1869
                }
1870
            }
1871
        }
1872

1873
        dst.emplace_back(
5,659✔
1874
            li.li_file_range.fr_offset, log_tv, level, mod_index, opid);
5,659✔
1875

1876
        if (orig_lock != curr_fmt) {
5,659✔
1877
            uint32_t lock_line;
1878

1879
            log_debug("%s:%zu: changing pattern lock %d -> (%d)%s",
556✔
1880
                      lf.get_unique_path().c_str(),
1881
                      dst.size() - 1,
1882
                      orig_lock,
1883
                      curr_fmt,
1884
                      this->elf_pattern_order[curr_fmt]->p_name.c_str());
1885
            if (this->lf_pattern_locks.empty()) {
556✔
1886
                lock_line = 0;
543✔
1887
            } else {
1888
                lock_line = dst.size() - 1;
13✔
1889
            }
1890
            this->lf_pattern_locks.emplace_back(lock_line, curr_fmt);
556✔
1891
        }
1892
        return scan_match{1000};
5,659✔
1893
    }
1894

1895
    if (this->lf_specialized && !this->lf_multiline) {
576,685✔
1896
        const auto& last_line = dst.back();
1✔
1897

1898
        log_debug("%s: invalid line %d file_offset=%" PRIu64,
1✔
1899
                  lf.get_filename().c_str(),
1900
                  dst.size(),
1901
                  li.li_file_range.fr_offset);
1902
        dst.emplace_back(li.li_file_range.fr_offset,
1✔
UNCOV
1903
                         last_line.get_timeval(),
×
1904
                         log_level_t::LEVEL_INVALID);
1✔
1905

1906
        return scan_match{0};
1✔
1907
    }
1908

1909
    return scan_no_match{"no patterns matched"};
576,684✔
1910
}
1911

1912
uint8_t
1913
external_log_format::module_scan(string_fragment body_cap,
159✔
1914
                                 const intern_string_t& mod_name)
1915
{
1916
    uint8_t mod_index;
1917
    body_cap = body_cap.trim();
159✔
1918
    auto& ext_fmts = GRAPH_ORDERED_FORMATS;
159✔
1919
    module_format mf;
159✔
1920

1921
    for (auto& elf : ext_fmts) {
9,880✔
1922
        int curr_fmt = -1, fmt_lock = -1;
9,762✔
1923

1924
        while (::next_format(elf->elf_pattern_order, curr_fmt, fmt_lock)) {
27,620✔
1925
            thread_local auto md = lnav::pcre2pp::match_data::unitialized();
17,899✔
1926

1927
            auto& fpat = elf->elf_pattern_order[curr_fmt];
17,899✔
1928
            auto& pat = fpat->p_pcre;
17,899✔
1929

1930
            if (!fpat->p_module_format) {
17,899✔
1931
                continue;
17,858✔
1932
            }
1933

1934
            auto match_res = pat.pp_value->capture_from(body_cap)
435✔
1935
                                 .into(md)
435✔
1936
                                 .matches(PCRE2_NO_UTF_CHECK)
870✔
1937
                                 .ignore_error();
435✔
1938
            if (!match_res) {
435✔
1939
                continue;
394✔
1940
            }
1941

1942
            log_debug("%s:module format found -- %s (%d)",
41✔
1943
                      mod_name.get(),
1944
                      elf->get_name().get(),
1945
                      elf->lf_mod_index);
1946

1947
            mod_index = elf->lf_mod_index;
41✔
1948
            mf.mf_mod_format = elf->specialized(curr_fmt);
41✔
1949
            MODULE_FORMATS[mod_name] = mf;
41✔
1950

1951
            return mod_index;
41✔
1952
        }
1953
    }
1954

1955
    MODULE_FORMATS[mod_name] = mf;
118✔
1956

1957
    return 0;
118✔
1958
}
159✔
1959

1960
void
1961
external_log_format::annotate(logfile* lf,
6,870✔
1962
                              uint64_t line_number,
1963
                              string_attrs_t& sa,
1964
                              logline_value_vector& values,
1965
                              bool annotate_module) const
1966
{
1967
    thread_local auto md = lnav::pcre2pp::match_data::unitialized();
6,870✔
1968

1969
    auto& line = values.lvv_sbr;
6,870✔
1970
    line_range lr;
6,870✔
1971

1972
    line.erase_ansi();
6,870✔
1973
    if (this->elf_type != elf_type_t::ELF_TYPE_TEXT) {
6,870✔
1974
        if (this->jlf_cached_opts.full_message) {
487✔
1975
            values = this->jlf_line_values;
119✔
1976
            sa = this->jlf_line_attrs;
119✔
1977
        } else {
1978
            values.lvv_sbr = this->jlf_line_values.lvv_sbr.clone();
368✔
1979
            for (const auto& llv : this->jlf_line_values.lvv_values) {
3,493✔
1980
                if (this->jlf_cached_sub_range.contains(llv.lv_origin)) {
3,125✔
1981
                    values.lvv_values.emplace_back(llv);
729✔
1982
                    values.lvv_values.back().lv_origin.shift(
729✔
1983
                        this->jlf_cached_sub_range.lr_start,
729✔
1984
                        -this->jlf_cached_sub_range.lr_start);
729✔
1985
                }
1986
            }
1987
            for (const auto& attr : this->jlf_line_attrs) {
2,006✔
1988
                if (this->jlf_cached_sub_range.contains(attr.sa_range)) {
1,638✔
1989
                    sa.emplace_back(attr);
569✔
1990
                    sa.back().sa_range.shift(
569✔
1991
                        this->jlf_cached_sub_range.lr_start,
569✔
1992
                        -this->jlf_cached_sub_range.lr_start);
569✔
1993
                }
1994
            }
1995
            values.lvv_opid_value = this->jlf_line_values.lvv_opid_value;
368✔
1996
            values.lvv_opid_provenance
1997
                = this->jlf_line_values.lvv_opid_provenance;
368✔
1998
        }
1999
        log_format::annotate(lf, line_number, sa, values, annotate_module);
487✔
2000
        return;
1,999✔
2001
    }
2002

2003
    if (line.empty()) {
6,383✔
2004
        return;
5✔
2005
    }
2006

2007
    values.lvv_values.reserve(this->elf_value_defs.size());
6,378✔
2008

2009
    int pat_index = this->pattern_index_for_line(line_number);
6,378✔
2010
    const auto& pat = *this->elf_pattern_order[pat_index];
6,378✔
2011

2012
    sa.reserve(pat.p_pcre.pp_value->get_capture_count());
6,378✔
2013
    auto match_res
2014
        = pat.p_pcre.pp_value->capture_from(line.to_string_fragment())
6,378✔
2015
              .into(md)
6,378✔
2016
              .matches(PCRE2_NO_UTF_CHECK)
12,756✔
2017
              .ignore_error();
6,378✔
2018
    if (!match_res) {
6,378✔
2019
        // A continued line still needs a body.
2020
        lr.lr_start = 0;
1,507✔
2021
        lr.lr_end = line.length();
1,507✔
2022
        sa.emplace_back(lr, SA_BODY.value());
1,507✔
2023
        if (!this->lf_multiline) {
1,507✔
2024
            auto len
UNCOV
2025
                = pat.p_pcre.pp_value->match_partial(line.to_string_fragment());
×
UNCOV
2026
            sa.emplace_back(
×
UNCOV
2027
                line_range{(int) len, -1},
×
UNCOV
2028
                SA_INVALID.value("Log line does not match any pattern"));
×
2029
        }
2030
        return;
1,507✔
2031
    }
2032

2033
    std::optional<string_fragment> module_cap;
4,871✔
2034
    if (!pat.p_module_format) {
4,871✔
2035
        auto ts_cap = md[pat.p_timestamp_field_index];
4,864✔
2036
        if (ts_cap) {
4,864✔
2037
            sa.emplace_back(to_line_range(ts_cap.value()), L_TIMESTAMP.value());
4,864✔
2038
        }
2039

2040
        if (pat.p_module_field_index != -1) {
4,864✔
2041
            module_cap = md[pat.p_module_field_index];
372✔
2042
            if (module_cap) {
372✔
2043
                sa.emplace_back(to_line_range(module_cap.value()),
370✔
2044
                                L_MODULE.value());
740✔
2045
            }
2046
        }
2047

2048
        auto opid_cap = md[pat.p_opid_field_index];
4,864✔
2049
        if (opid_cap && !opid_cap->empty()) {
4,864✔
2050
            sa.emplace_back(to_line_range(opid_cap.value()), L_OPID.value());
4,149✔
2051
            values.lvv_opid_value = opid_cap->to_string();
4,149✔
2052
            values.lvv_opid_provenance
2053
                = logline_value_vector::opid_provenance::file;
4,149✔
2054
        }
2055
    }
2056

2057
    auto body_cap = md[pat.p_body_field_index];
4,871✔
2058
    auto level_cap = md[pat.p_level_field_index];
4,871✔
2059
    auto src_file_cap = md[pat.p_src_file_field_index];
4,871✔
2060
    auto src_line_cap = md[pat.p_src_line_field_index];
4,871✔
2061

2062
    if (level_cap
4,871✔
2063
        && (!body_cap
9,694✔
2064
            || (body_cap && level_cap->sf_begin != body_cap->sf_begin)))
9,694✔
2065
    {
2066
        sa.emplace_back(to_line_range(level_cap.value()), L_LEVEL.value());
4,451✔
2067
    }
2068

2069
    if (src_file_cap) {
4,871✔
2070
        sa.emplace_back(to_line_range(src_file_cap.value()),
49✔
2071
                        SA_SRC_FILE.value());
98✔
2072
    }
2073
    if (src_line_cap) {
4,871✔
2074
        sa.emplace_back(to_line_range(src_line_cap.value()),
49✔
2075
                        SA_SRC_LINE.value());
98✔
2076
    }
2077

2078
    for (size_t lpc = 0; lpc < pat.p_value_by_index.size(); lpc++) {
56,177✔
2079
        const auto& ivd = pat.p_value_by_index[lpc];
51,306✔
2080
        const scaling_factor* scaling = nullptr;
51,306✔
2081
        auto cap = md[ivd.ivd_index];
51,306✔
2082
        const auto& vd = *ivd.ivd_value_def;
51,306✔
2083

2084
        if (ivd.ivd_unit_field_index >= 0) {
51,306✔
2085
            auto unit_cap = md[ivd.ivd_unit_field_index];
10✔
2086

2087
            if (unit_cap) {
10✔
2088
                intern_string_t unit_val
2089
                    = intern_string::lookup(unit_cap.value());
10✔
2090
                auto unit_iter = vd.vd_unit_scaling.find(unit_val);
10✔
2091
                if (unit_iter != vd.vd_unit_scaling.end()) {
10✔
2092
                    const auto& sf = unit_iter->second;
10✔
2093

2094
                    scaling = &sf;
10✔
2095
                }
2096
            }
2097
        }
2098

2099
        if (cap) {
51,306✔
2100
            values.lvv_values.emplace_back(
83,740✔
2101
                vd.vd_meta, line, to_line_range(cap.value()));
41,870✔
2102
            values.lvv_values.back().apply_scaling(scaling);
41,870✔
2103
        } else {
2104
            values.lvv_values.emplace_back(vd.vd_meta);
9,436✔
2105
        }
2106
        if (pat.p_module_format) {
51,306✔
2107
            values.lvv_values.back().lv_meta.lvm_from_module = true;
52✔
2108
        }
2109
    }
2110

2111
    bool did_mod_annotate_body = false;
4,871✔
2112
    if (annotate_module && module_cap && body_cap && body_cap->is_valid()) {
4,871✔
2113
        intern_string_t mod_name = intern_string::lookup(module_cap.value());
94✔
2114
        auto mod_iter = MODULE_FORMATS.find(mod_name);
94✔
2115

2116
        if (mod_iter != MODULE_FORMATS.end()
94✔
2117
            && mod_iter->second.mf_mod_format != nullptr)
94✔
2118
        {
2119
            auto& mf = mod_iter->second;
5✔
2120
            auto narrow_res
2121
                = line.narrow(body_cap->sf_begin, body_cap->length());
5✔
2122
            auto pre_mod_values_size = values.lvv_values.size();
5✔
2123
            auto pre_mod_sa_size = sa.size();
5✔
2124
            mf.mf_mod_format->annotate(lf, line_number, sa, values, false);
5✔
2125
            for (size_t lpc = pre_mod_values_size;
35✔
2126
                 lpc < values.lvv_values.size();
35✔
2127
                 lpc++)
2128
            {
2129
                values.lvv_values[lpc].lv_origin.shift(0, body_cap->sf_begin);
30✔
2130
            }
2131
            for (size_t lpc = pre_mod_sa_size; lpc < sa.size(); lpc++) {
10✔
2132
                sa[lpc].sa_range.shift(0, body_cap->sf_begin);
5✔
2133
            }
2134
            line.widen(narrow_res);
5✔
2135
            did_mod_annotate_body = true;
5✔
2136
        }
2137
    }
2138
    if (!did_mod_annotate_body) {
4,871✔
2139
        if (body_cap && body_cap->is_valid()) {
4,866✔
2140
            lr = to_line_range(body_cap.value());
4,849✔
2141
        } else {
2142
            lr.lr_start = line.length();
17✔
2143
            lr.lr_end = line.length();
17✔
2144
        }
2145
        sa.emplace_back(lr, SA_BODY.value());
4,866✔
2146
    }
2147

2148
    log_format::annotate(lf, line_number, sa, values, annotate_module);
4,871✔
2149
}
2150

2151
void
2152
external_log_format::rewrite(exec_context& ec,
43✔
2153
                             shared_buffer_ref& line,
2154
                             string_attrs_t& sa,
2155
                             std::string& value_out)
2156
{
2157
    auto& values = *ec.ec_line_values;
43✔
2158

2159
    value_out.assign(line.get_data(), line.length());
43✔
2160

2161
    for (auto iter = values.lvv_values.begin(); iter != values.lvv_values.end();
259✔
2162
         ++iter)
216✔
2163
    {
2164
        if (!iter->lv_origin.is_valid()) {
216✔
2165
            log_debug("%d: not rewriting value with invalid origin -- %s",
24✔
2166
                      ec.ec_top_line,
2167
                      iter->lv_meta.lvm_name.get());
2168
            continue;
184✔
2169
        }
2170

2171
        auto vd_iter = this->elf_value_defs.find(iter->lv_meta.lvm_name);
192✔
2172
        if (vd_iter == this->elf_value_defs.end()) {
192✔
2173
            log_debug("not rewriting undefined value -- %s",
×
2174
                      iter->lv_meta.lvm_name.get());
UNCOV
2175
            continue;
×
2176
        }
2177

2178
        const auto& vd = *vd_iter->second;
192✔
2179

2180
        if (vd.vd_rewriter.empty()) {
192✔
2181
            continue;
160✔
2182
        }
2183

2184
        auto _sg = ec.enter_source(
2185
            vd_iter->second->vd_rewrite_src_name, 1, vd.vd_rewriter);
32✔
2186
        std::string field_value;
32✔
2187

2188
        auto_mem<FILE> tmpout(fclose);
32✔
2189

2190
        tmpout = std::tmpfile();
32✔
2191
        if (!tmpout) {
32✔
UNCOV
2192
            log_error("unable to create temporary file");
×
UNCOV
2193
            return;
×
2194
        }
2195
        fcntl(fileno(tmpout), F_SETFD, FD_CLOEXEC);
32✔
2196
        auto fd_copy = auto_fd::dup_of(fileno(tmpout));
32✔
2197
        fd_copy.close_on_exec();
32✔
2198
        auto ec_out = std::make_pair(tmpout.release(), fclose);
32✔
2199
        {
2200
            exec_context::output_guard og(ec, "tmp", ec_out);
64✔
2201

2202
            auto exec_res = execute_any(ec, vd.vd_rewriter);
32✔
2203
            if (exec_res.isOk()) {
32✔
2204
                field_value = exec_res.unwrap();
32✔
2205
            } else {
UNCOV
2206
                field_value = exec_res.unwrapErr().to_attr_line().get_string();
×
2207
            }
2208
        }
32✔
2209
        struct stat st;
2210
        fstat(fd_copy.get(), &st);
32✔
2211
        if (st.st_size > 0) {
32✔
2212
            auto buf = auto_buffer::alloc(st.st_size);
2✔
2213

2214
            buf.resize(st.st_size);
2✔
2215
            pread(fd_copy.get(), buf.in(), st.st_size, 0);
2✔
2216
            field_value = buf.to_string();
2✔
2217
        }
2✔
2218
        value_out.erase(iter->lv_origin.lr_start, iter->lv_origin.length());
32✔
2219

2220
        int32_t shift_amount
2221
            = ((int32_t) field_value.length()) - iter->lv_origin.length();
32✔
2222
        auto orig_lr = iter->lv_origin;
32✔
2223
        value_out.insert(iter->lv_origin.lr_start, field_value);
32✔
2224
        for (auto shift_iter = values.lvv_values.begin();
32✔
2225
             shift_iter != values.lvv_values.end();
180✔
2226
             ++shift_iter)
148✔
2227
        {
2228
            shift_iter->lv_origin.shift_range(orig_lr, shift_amount);
148✔
2229
        }
2230
        shift_string_attrs(sa, orig_lr, shift_amount);
32✔
2231
    }
32✔
2232
}
2233

2234
static int
2235
read_json_field(yajlpp_parse_context* ypc,
46,693✔
2236
                const unsigned char* str,
2237
                size_t len,
2238
                yajl_string_props_t* props)
2239
{
2240
    auto* jlu = (json_log_userdata*) ypc->ypc_userdata;
46,693✔
2241
    timeval tv_out;
2242
    const auto frag = string_fragment::from_bytes(str, len);
46,693✔
2243
    intern_string_t field_name;
46,693✔
2244
    const auto* vd = jlu->get_field_def(ypc);
46,693✔
2245

2246
    if (vd != nullptr) {
46,693✔
2247
        field_name = vd->vd_meta.lvm_name;
2,521✔
2248
    }
2249

2250
    if (field_name.empty()) {
46,693✔
2251
    } else if (jlu->jlu_format->lf_timestamp_field == field_name) {
2,521✔
2252
        const auto* last = jlu->jlu_format->lf_date_time.scan(
631✔
2253
            (const char*) str,
2254
            len,
2255
            jlu->jlu_format->get_timestamp_formats(),
631✔
2256
            &jlu->jlu_exttm,
2257
            tv_out);
2258
        if (last == nullptr) {
631✔
2259
            auto ls = jlu->jlu_format->lf_date_time.unlock();
22✔
2260
            if ((last = jlu->jlu_format->lf_date_time.scan(
22✔
2261
                     (const char*) str,
2262
                     len,
2263
                     jlu->jlu_format->get_timestamp_formats(),
22✔
2264
                     &jlu->jlu_exttm,
2265
                     tv_out))
2266
                == nullptr)
22✔
2267
            {
UNCOV
2268
                jlu->jlu_format->lf_date_time.relock(ls);
×
2269
            }
2270
            if (last != nullptr) {
22✔
2271
                auto old_flags
22✔
2272
                    = jlu->jlu_format->lf_timestamp_flags & DATE_TIME_SET_FLAGS;
22✔
2273
                auto new_flags = jlu->jlu_exttm.et_flags & DATE_TIME_SET_FLAGS;
22✔
2274

2275
                // It is unlikely a valid timestamp would lose much
2276
                // precision.
2277
                if (new_flags != old_flags) {
22✔
UNCOV
2278
                    last = nullptr;
×
2279
                }
2280
            }
2281
        }
2282
        if (last != nullptr) {
631✔
2283
            jlu->jlu_format->lf_timestamp_flags = jlu->jlu_exttm.et_flags;
631✔
2284
            jlu->jlu_base_line->set_time(tv_out);
631✔
2285
        }
2286
    } else if (jlu->jlu_format->elf_level_pointer.pp_value != nullptr) {
1,890✔
2287
        if (jlu->jlu_format->elf_level_pointer.pp_value
14✔
2288
                ->find_in(field_name.to_string_fragment(), PCRE2_NO_UTF_CHECK)
14✔
2289
                .ignore_error()
14✔
2290
                .has_value())
7✔
2291
        {
UNCOV
2292
            jlu->jlu_base_line->set_level(
×
UNCOV
2293
                jlu->jlu_format->convert_level(frag, jlu->jlu_batch_context));
×
2294
        }
2295
    }
2296
    if (jlu->jlu_format->elf_level_field == field_name) {
46,693✔
2297
        jlu->jlu_base_line->set_level(
263✔
2298
            jlu->jlu_format->convert_level(frag, jlu->jlu_batch_context));
263✔
2299
    }
2300
    if (jlu->jlu_format->elf_opid_field == field_name) {
46,693✔
2301
        jlu->jlu_base_line->set_opid(frag.hash());
31,022✔
2302

2303
        auto& sbc = *jlu->jlu_batch_context;
31,022✔
2304
        auto opid_iter = sbc.sbc_opids.los_opid_ranges.find(frag);
31,022✔
2305
        if (opid_iter == sbc.sbc_opids.los_opid_ranges.end()) {
31,022✔
2306
            jlu->jlu_opid_frag = frag.to_owned(sbc.sbc_allocator);
30,976✔
2307
        } else {
2308
            jlu->jlu_opid_frag = opid_iter->first;
46✔
2309
        }
2310
    }
2311
    if (!jlu->jlu_format->elf_subid_field.empty()
46,693✔
2312
        && jlu->jlu_format->elf_subid_field == field_name)
46,693✔
2313
    {
UNCOV
2314
        jlu->jlu_subid = frag.to_string();
×
2315
    }
2316

2317
    if (vd != nullptr && vd->vd_is_desc_field) {
46,693✔
UNCOV
2318
        auto frag_copy = frag.to_owned(jlu->jlu_format->lf_desc_allocator);
×
2319

UNCOV
2320
        jlu->jlu_format->lf_desc_captures.emplace(field_name, frag_copy);
×
2321
    }
2322

2323
    jlu->add_sub_lines_for(vd, ypc->is_level(1), std::nullopt, str, len, props);
46,693✔
2324

2325
    return 1;
46,693✔
2326
}
2327

2328
static int
2329
rewrite_json_field(yajlpp_parse_context* ypc,
23,488✔
2330
                   const unsigned char* str,
2331
                   size_t len,
2332
                   yajl_string_props_t* props)
2333
{
2334
    static const intern_string_t body_name = intern_string::lookup("body", -1);
23,488✔
2335
    auto* jlu = (json_log_userdata*) ypc->ypc_userdata;
23,488✔
2336
    intern_string_t field_name;
23,488✔
2337
    const auto* vd = jlu->get_field_def(ypc);
23,488✔
2338

2339
    if (!ypc->is_level(1) && vd == nullptr) {
23,488✔
2340
        return 1;
17,018✔
2341
    }
2342
    if (vd != nullptr) {
6,470✔
2343
        field_name = vd->vd_meta.lvm_name;
6,078✔
2344
    } else {
2345
        field_name = ypc->get_path();
392✔
2346
    }
2347

2348
    if (jlu->jlu_format->elf_opid_field == field_name) {
6,470✔
2349
        auto frag = string_fragment::from_bytes(str, len);
276✔
2350
        jlu->jlu_format->jlf_line_values.lvv_opid_value = frag.to_string();
276✔
2351
        jlu->jlu_format->jlf_line_values.lvv_opid_provenance
276✔
2352
            = logline_value_vector::opid_provenance::file;
276✔
2353
    }
2354
    if (jlu->jlu_format->lf_timestamp_field == field_name) {
6,470✔
2355
        char time_buf[64];
2356

2357
        // TODO add a timeval kind to logline_value
2358
        if (jlu->jlu_line->is_time_skewed()
1,464✔
2359
            || (jlu->jlu_format->lf_timestamp_flags
2,928✔
2360
                & (ETF_MICROS_SET | ETF_NANOS_SET | ETF_ZONE_SET)))
1,464✔
2361
        {
2362
            timeval tv;
2363

2364
            const auto* last = jlu->jlu_format->lf_date_time.scan(
1,464✔
2365
                (const char*) str,
2366
                len,
2367
                jlu->jlu_format->get_timestamp_formats(),
1,464✔
2368
                &jlu->jlu_exttm,
2369
                tv);
2370
            if (last == nullptr) {
1,464✔
2371
                auto ls = jlu->jlu_format->lf_date_time.unlock();
58✔
2372
                if ((last = jlu->jlu_format->lf_date_time.scan(
58✔
2373
                         (const char*) str,
2374
                         len,
2375
                         jlu->jlu_format->get_timestamp_formats(),
58✔
2376
                         &jlu->jlu_exttm,
2377
                         tv))
2378
                    == nullptr)
58✔
2379
                {
UNCOV
2380
                    jlu->jlu_format->lf_date_time.relock(ls);
×
2381
                }
2382
            }
2383
            if (!jlu->jlu_subline_opts.hash_hack) {
1,464✔
2384
                if (jlu->jlu_exttm.et_flags & ETF_ZONE_SET
1,464✔
2385
                    && jlu->jlu_format->lf_date_time.dts_zoned_to_local)
1,464✔
2386
                {
2387
                    jlu->jlu_exttm.et_flags &= ~ETF_Z_IS_UTC;
1,464✔
2388
                }
2389
                jlu->jlu_exttm.et_gmtoff
2390
                    = jlu->jlu_format->lf_date_time.dts_local_offset_cache;
1,464✔
2391
            }
2392
            jlu->jlu_format->lf_date_time.ftime(
1,464✔
2393
                time_buf,
2394
                sizeof(time_buf),
2395
                jlu->jlu_format->get_timestamp_formats(),
1,464✔
2396
                jlu->jlu_exttm);
1,464✔
2397
        } else {
UNCOV
2398
            sql_strftime(
×
UNCOV
2399
                time_buf, sizeof(time_buf), jlu->jlu_line->get_timeval(), 'T');
×
2400
        }
2401
        jlu->jlu_format->jlf_line_values.lvv_values.emplace_back(
2,928✔
2402
            jlu->jlu_format->get_value_meta(field_name,
2,928✔
2403
                                            value_kind_t::VALUE_TEXT),
2404
            std::string{time_buf});
4,392✔
2405
    } else if (jlu->jlu_shared_buffer.contains((const char*) str)) {
5,006✔
2406
        auto str_offset = (int) ((const char*) str - jlu->jlu_line_value);
4,606✔
2407
        if (field_name == jlu->jlu_format->elf_body_field) {
4,606✔
2408
            jlu->jlu_format->jlf_line_values.lvv_values.emplace_back(
766✔
2409
                logline_value_meta(body_name,
1,532✔
2410
                                   value_kind_t::VALUE_TEXT,
UNCOV
2411
                                   logline_value_meta::internal_column{},
×
2412
                                   jlu->jlu_format),
766✔
2413
                string_fragment::from_byte_range(
1,532✔
2414
                    jlu->jlu_shared_buffer.get_data(),
766✔
2415
                    str_offset,
2416
                    str_offset + len));
766✔
2417
        }
2418

2419
        jlu->jlu_format->jlf_line_values.lvv_values.emplace_back(
4,606✔
2420
            jlu->jlu_format->get_value_meta(field_name,
9,212✔
2421
                                            value_kind_t::VALUE_TEXT),
2422
            string_fragment::from_byte_range(jlu->jlu_shared_buffer.get_data(),
9,212✔
2423
                                             str_offset,
2424
                                             str_offset + len));
4,606✔
2425
    } else {
2426
        if (field_name == jlu->jlu_format->elf_body_field) {
400✔
2427
            jlu->jlu_format->jlf_line_values.lvv_values.emplace_back(
315✔
2428
                logline_value_meta(body_name,
630✔
2429
                                   value_kind_t::VALUE_TEXT,
UNCOV
2430
                                   logline_value_meta::internal_column{},
×
2431
                                   jlu->jlu_format),
315✔
2432
                std::string{(const char*) str, len});
1,260✔
2433
        }
2434

2435
        jlu->jlu_format->jlf_line_values.lvv_values.emplace_back(
400✔
2436
            jlu->jlu_format->get_value_meta(ypc, vd, value_kind_t::VALUE_TEXT),
800✔
2437
            std::string{(const char*) str, len});
1,600✔
2438
    }
2439

2440
    return 1;
6,470✔
2441
}
2442

2443
void
2444
external_log_format::get_subline(const logline& ll,
21,344✔
2445
                                 shared_buffer_ref& sbr,
2446
                                 subline_options opts)
2447
{
2448
    if (this->elf_type == elf_type_t::ELF_TYPE_TEXT) {
21,344✔
2449
        return;
17,955✔
2450
    }
2451

2452
    if (this->jlf_cached_offset != ll.get_offset()
3,389✔
2453
        || this->jlf_cached_opts != opts)
3,389✔
2454
    {
2455
        auto& ypc = *(this->jlf_parse_context);
1,571✔
2456
        yajl_handle handle = this->jlf_yajl_handle.get();
1,571✔
2457
        json_log_userdata jlu(sbr, nullptr);
1,571✔
2458

2459
        jlu.jlu_subline_opts = opts;
1,571✔
2460

2461
        this->jlf_share_manager.invalidate_refs();
1,571✔
2462
        this->jlf_cached_line.clear();
1,571✔
2463
        this->jlf_line_values.clear();
1,571✔
2464
        this->jlf_line_offsets.clear();
1,571✔
2465
        this->jlf_line_attrs.clear();
1,571✔
2466

2467
        auto line_frag = sbr.to_string_fragment();
1,571✔
2468

2469
        if (!line_frag.startswith("{")) {
1,571✔
2470
            this->jlf_cached_line.resize(line_frag.length());
70✔
2471
            memcpy(this->jlf_cached_line.data(),
70✔
2472
                   line_frag.data(),
70✔
2473
                   line_frag.length());
70✔
2474
            this->jlf_line_values.clear();
70✔
2475
            sbr.share(this->jlf_share_manager,
140✔
2476
                      &this->jlf_cached_line[0],
70✔
2477
                      this->jlf_cached_line.size());
2478
            this->jlf_line_values.lvv_sbr = sbr.clone();
70✔
2479
            this->jlf_line_attrs.emplace_back(
70✔
UNCOV
2480
                line_range{0, -1},
×
2481
                SA_INVALID.value(fmt::format(
140✔
2482
                    FMT_STRING("line at offset {} is not a JSON-line"),
140✔
2483
                    ll.get_offset())));
70✔
2484
            return;
70✔
2485
        }
2486

2487
        yajl_reset(handle);
1,501✔
2488
        ypc.set_static_handler(json_log_rewrite_handlers.jpc_children[0]);
1,501✔
2489
        ypc.ypc_userdata = &jlu;
1,501✔
2490
        ypc.ypc_ignore_unused = true;
1,501✔
2491
        ypc.ypc_alt_callbacks.yajl_start_array = json_array_start;
1,501✔
2492
        ypc.ypc_alt_callbacks.yajl_end_array = json_array_end;
1,501✔
2493
        ypc.ypc_alt_callbacks.yajl_start_map = json_array_start;
1,501✔
2494
        ypc.ypc_alt_callbacks.yajl_end_map = json_array_end;
1,501✔
2495
        jlu.jlu_format = this;
1,501✔
2496
        jlu.jlu_line = &ll;
1,501✔
2497
        jlu.jlu_handle = handle;
1,501✔
2498
        jlu.jlu_line_value = sbr.get_data();
1,501✔
2499
        jlu.jlu_format_hits.resize(this->jlf_line_format.size());
1,501✔
2500

2501
        yajl_status parse_status = yajl_parse(
3,002✔
2502
            handle, (const unsigned char*) sbr.get_data(), sbr.length());
1,501✔
2503
        if (parse_status != yajl_status_ok
1,501✔
2504
            || yajl_complete_parse(handle) != yajl_status_ok)
1,501✔
2505
        {
2506
            unsigned char* msg;
2507
            std::string full_msg;
16✔
2508

2509
            msg = yajl_get_error(
32✔
2510
                handle, 1, (const unsigned char*) sbr.get_data(), sbr.length());
16✔
2511
            if (msg != nullptr) {
16✔
2512
                full_msg = fmt::format(
16✔
2513
                    FMT_STRING("[offset: {}] {}\n{}"),
32✔
2514
                    ll.get_offset(),
16✔
2515
                    fmt::string_view{sbr.get_data(), sbr.length()},
16✔
2516
                    reinterpret_cast<char*>(msg));
32✔
2517
                yajl_free_error(handle, msg);
16✔
2518
            }
2519

2520
            this->jlf_cached_line.resize(full_msg.size());
16✔
2521
            memcpy(
16✔
2522
                this->jlf_cached_line.data(), full_msg.data(), full_msg.size());
16✔
2523
            this->jlf_line_values.clear();
16✔
2524
            this->jlf_line_attrs.emplace_back(
16✔
UNCOV
2525
                line_range{0, -1},
×
2526
                SA_INVALID.value("JSON line failed to parse"));
32✔
2527
        } else {
16✔
2528
            std::vector<logline_value>::iterator lv_iter;
1,485✔
2529
            bool used_values[this->jlf_line_values.lvv_values.size()];
2,970✔
2530
            struct line_range lr;
1,485✔
2531

2532
            memset(used_values, 0, sizeof(used_values));
1,485✔
2533
            for (lv_iter = this->jlf_line_values.lvv_values.begin();
1,485✔
2534
                 lv_iter != this->jlf_line_values.lvv_values.end();
11,021✔
2535
                 ++lv_iter)
9,536✔
2536
            {
2537
                lv_iter->lv_meta.lvm_format = this;
9,536✔
2538
            }
2539
            if (jlu.jlu_opid_frag) {
1,485✔
2540
                this->jlf_line_values.lvv_opid_value
UNCOV
2541
                    = jlu.jlu_opid_frag->to_string();
×
2542
                this->jlf_line_values.lvv_opid_provenance
UNCOV
2543
                    = logline_value_vector::opid_provenance::file;
×
2544
            }
2545

2546
            int sub_offset = this->jlf_line_format_init_count;
1,485✔
2547
            for (const auto& jfe : this->jlf_line_format) {
15,456✔
2548
                static const intern_string_t ts_field
2549
                    = intern_string::lookup("__timestamp__", -1);
13,971✔
2550
                static const intern_string_t level_field
2551
                    = intern_string::lookup("__level__");
14,045✔
2552
                size_t begin_size = this->jlf_cached_line.size();
13,971✔
2553

2554
                switch (jfe.jfe_type) {
13,971✔
2555
                    case json_log_field::CONSTANT:
3,507✔
2556
                        this->json_append_to_cache(
3,507✔
2557
                            jfe.jfe_default_value.c_str(),
2558
                            jfe.jfe_default_value.size());
3,507✔
2559
                        break;
3,507✔
2560
                    case json_log_field::VARIABLE:
10,464✔
2561
                        lv_iter = find_if(
10,464✔
2562
                            this->jlf_line_values.lvv_values.begin(),
2563
                            this->jlf_line_values.lvv_values.end(),
2564
                            logline_value_name_cmp(&jfe.jfe_value.pp_value));
2565
                        if (lv_iter != this->jlf_line_values.lvv_values.end()) {
10,464✔
2566
                            auto str = lv_iter->to_string();
5,026✔
2567
                            value_def* vd = nullptr;
5,026✔
2568

2569
                            if (lv_iter->lv_meta.lvm_values_index) {
5,026✔
2570
                                vd = this->elf_value_def_order
2571
                                         [lv_iter->lv_meta.lvm_values_index
5,026✔
2572
                                              .value()]
5,026✔
2573
                                             .get();
5,026✔
2574
                            }
2575
                            while (endswith(str, "\n")) {
5,288✔
2576
                                str.pop_back();
262✔
2577
                            }
2578
                            size_t nl_pos = str.find('\n');
5,026✔
2579

2580
                            if (!jfe.jfe_prefix.empty()) {
5,026✔
2581
                                this->json_append_to_cache(jfe.jfe_prefix);
1,509✔
2582
                            }
2583
                            lr.lr_start = this->jlf_cached_line.size();
5,026✔
2584

2585
                            if ((int) str.size() > jfe.jfe_max_width) {
5,026✔
2586
                                switch (jfe.jfe_overflow) {
176✔
2587
                                    case json_format_element::overflow_t::
137✔
2588
                                        ABBREV: {
2589
                                        size_t new_size
2590
                                            = abbreviate_str(&str[0],
137✔
2591
                                                             str.size(),
2592
                                                             jfe.jfe_max_width);
137✔
2593
                                        str.resize(new_size);
137✔
2594
                                        this->json_append(jfe, vd, str);
137✔
2595
                                        break;
137✔
2596
                                    }
2597
                                    case json_format_element::overflow_t::
36✔
2598
                                        TRUNCATE: {
2599
                                        this->json_append_to_cache(
36✔
2600
                                            str.c_str(), jfe.jfe_max_width);
36✔
2601
                                        break;
36✔
2602
                                    }
2603
                                    case json_format_element::overflow_t::
3✔
2604
                                        DOTDOT: {
2605
                                        size_t middle
3✔
2606
                                            = (jfe.jfe_max_width / 2) - 1;
3✔
2607
                                        this->json_append_to_cache(str.c_str(),
3✔
2608
                                                                   middle);
2609
                                        this->json_append_to_cache("..", 2);
3✔
2610
                                        size_t rest
3✔
2611
                                            = (jfe.jfe_max_width - middle - 2);
3✔
2612
                                        this->json_append_to_cache(
6✔
2613
                                            str.c_str() + str.size() - rest,
3✔
2614
                                            rest);
2615
                                        break;
3✔
2616
                                    }
UNCOV
2617
                                    case json_format_element::overflow_t::
×
2618
                                        LASTWORD: {
2619
                                        size_t new_size
UNCOV
2620
                                            = last_word_str(&str[0],
×
2621
                                                            str.size(),
UNCOV
2622
                                                            jfe.jfe_max_width);
×
UNCOV
2623
                                        str.resize(new_size);
×
UNCOV
2624
                                        this->json_append(jfe, vd, str);
×
UNCOV
2625
                                        break;
×
2626
                                    }
2627
                                }
2628
                            } else {
2629
                                sub_offset
2630
                                    += std::count(str.begin(), str.end(), '\n');
4,850✔
2631
                                this->json_append(jfe, vd, str);
4,850✔
2632
                            }
2633

2634
                            if (nl_pos == std::string::npos || opts.full_message) {
5,026✔
2635
                                lr.lr_end = this->jlf_cached_line.size();
5,023✔
2636
                            } else {
2637
                                lr.lr_end = lr.lr_start + nl_pos;
3✔
2638
                            }
2639

2640
                            if (lv_iter->lv_meta.lvm_name
5,026✔
2641
                                == this->lf_timestamp_field)
5,026✔
2642
                            {
2643
                                this->jlf_line_attrs.emplace_back(
1,003✔
2644
                                    lr, L_TIMESTAMP.value());
2,006✔
2645
                            } else if (lv_iter->lv_meta.lvm_name
4,023✔
2646
                                       == this->elf_body_field)
4,023✔
2647
                            {
2648
                                this->jlf_line_attrs.emplace_back(
1,081✔
2649
                                lr, SA_BODY.value());
2,162✔
2650
                            } else if (lv_iter->lv_meta.lvm_name
2,942✔
2651
                                       == this->elf_src_file_field)
2,942✔
2652
                            {
2653
                                this->jlf_line_attrs.emplace_back(
8✔
2654
                                lr, SA_SRC_FILE.value());
16✔
2655
                            } else if (lv_iter->lv_meta.lvm_name
2,934✔
2656
                                       == this->elf_src_line_field)
2,934✔
2657
                            {
2658
                                this->jlf_line_attrs.emplace_back(
8✔
2659
                                    lr, SA_SRC_LINE.value());
16✔
2660
                            } else if (lv_iter->lv_meta.lvm_name
2,926✔
2661
                                       == this->elf_level_field)
2,926✔
2662
                            {
2663
                                this->jlf_line_attrs.emplace_back(
1,037✔
2664
                                    lr, L_LEVEL.value());
2,074✔
2665
                            } else if (lv_iter->lv_meta.lvm_name
3,778✔
2666
                                           == this->elf_opid_field
1,889✔
2667
                                       && !lr.empty())
1,889✔
2668
                            {
2669
                                this->jlf_line_attrs.emplace_back(
276✔
2670
                                    lr, L_OPID.value());
552✔
2671
                            }
2672
                            lv_iter->lv_origin = lr;
5,026✔
2673
                            lv_iter->lv_sub_offset = sub_offset;
5,026✔
2674
                            used_values[std::distance(
5,026✔
2675
                                this->jlf_line_values.lvv_values.begin(),
2676
                                lv_iter)]
2677
                                = true;
5,026✔
2678

2679
                            if (!jfe.jfe_suffix.empty()) {
5,026✔
2680
                                this->json_append_to_cache(jfe.jfe_suffix);
486✔
2681
                            }
2682
                        } else if (jfe.jfe_value.pp_value == ts_field) {
10,464✔
2683
                            struct line_range lr;
627✔
2684
                            ssize_t ts_len;
2685
                            char ts[64];
2686
                            struct exttm et;
627✔
2687

2688
                            ll.to_exttm(et);
627✔
2689
                            et.et_nsec += jlu.jlu_exttm.et_nsec % 1000;
627✔
2690
                            et.et_gmtoff = jlu.jlu_exttm.et_gmtoff;
627✔
2691
                            et.et_flags |= jlu.jlu_exttm.et_flags;
627✔
2692
                            if (!jfe.jfe_prefix.empty()) {
627✔
2693
                                this->json_append_to_cache(jfe.jfe_prefix);
3✔
2694
                            }
2695
                            if (jfe.jfe_ts_format.empty()) {
627✔
2696
                                ts_len = this->lf_date_time.ftime(
468✔
2697
                                    ts,
2698
                                    sizeof(ts),
2699
                                    this->get_timestamp_formats(),
2700
                                    et);
2701
                            } else {
2702
                                ts_len = ftime_fmt(ts,
159✔
2703
                                                   sizeof(ts),
2704
                                                   jfe.jfe_ts_format.c_str(),
2705
                                                   et);
2706
                            }
2707
                            lr.lr_start = this->jlf_cached_line.size();
627✔
2708
                            this->json_append_to_cache(ts, ts_len);
627✔
2709
                            lr.lr_end = this->jlf_cached_line.size();
627✔
2710
                            this->jlf_line_attrs.emplace_back(
627✔
2711
                                lr, L_TIMESTAMP.value());
1,254✔
2712

2713
                            lv_iter = find_if(
627✔
2714
                                this->jlf_line_values.lvv_values.begin(),
2715
                                this->jlf_line_values.lvv_values.end(),
2716
                                logline_value_name_cmp(
2717
                                    &this->lf_timestamp_field));
627✔
2718
                            if (lv_iter
627✔
2719
                                != this->jlf_line_values.lvv_values.end())
627✔
2720
                            {
2721
                                used_values[distance(
627✔
2722
                                    this->jlf_line_values.lvv_values.begin(),
2723
                                    lv_iter)]
2724
                                    = true;
627✔
2725
                            }
2726
                            if (!jfe.jfe_suffix.empty()) {
627✔
2727
                                this->json_append_to_cache(jfe.jfe_suffix);
3✔
2728
                            }
2729
                        } else if (jfe.jfe_value.pp_value == level_field
4,811✔
2730
                                   || jfe.jfe_value.pp_value
9,542✔
2731
                                       == this->elf_level_field)
4,731✔
2732
                        {
2733
                            auto level_name = ll.get_level_name();
80✔
2734
                            lr.lr_start = this->jlf_cached_line.size();
80✔
2735
                            this->json_append(jfe, nullptr, level_name);
80✔
2736
                            if (jfe.jfe_auto_width) {
80✔
2737
                                this->json_append_to_cache(
60✔
2738
                                    MAX_LEVEL_NAME_LEN - level_name.length());
60✔
2739
                            }
2740
                            lr.lr_end = this->jlf_cached_line.size();
80✔
2741
                            this->jlf_line_attrs.emplace_back(lr,
80✔
2742
                                                              L_LEVEL.value());
160✔
2743
                        } else if (!jfe.jfe_default_value.empty()) {
4,731✔
2744
                            if (!jfe.jfe_prefix.empty()) {
796✔
2745
                                this->json_append_to_cache(jfe.jfe_prefix);
576✔
2746
                            }
2747
                            this->json_append(
796✔
2748
                                jfe, nullptr, jfe.jfe_default_value);
796✔
2749
                            if (!jfe.jfe_suffix.empty()) {
796✔
2750
                                this->json_append_to_cache(jfe.jfe_suffix);
96✔
2751
                            }
2752
                        }
2753

2754
                        switch (jfe.jfe_text_transform) {
10,464✔
2755
                            case external_log_format::json_format_element::
10,384✔
2756
                                transform_t::NONE:
2757
                                break;
10,384✔
2758
                            case external_log_format::json_format_element::
80✔
2759
                                transform_t::UPPERCASE:
2760
                                for (size_t cindex = begin_size;
80✔
2761
                                     cindex < this->jlf_cached_line.size();
643✔
2762
                                     cindex++)
2763
                                {
2764
                                    this->jlf_cached_line[cindex] = toupper(
563✔
2765
                                        this->jlf_cached_line[cindex]);
563✔
2766
                                }
2767
                                break;
80✔
UNCOV
2768
                            case external_log_format::json_format_element::
×
2769
                                transform_t::LOWERCASE:
2770
                                for (size_t cindex = begin_size;
×
UNCOV
2771
                                     cindex < this->jlf_cached_line.size();
×
2772
                                     cindex++)
2773
                                {
2774
                                    this->jlf_cached_line[cindex] = tolower(
×
2775
                                        this->jlf_cached_line[cindex]);
×
2776
                                }
2777
                                break;
×
UNCOV
2778
                            case external_log_format::json_format_element::
×
2779
                                transform_t::CAPITALIZE:
UNCOV
2780
                                for (size_t cindex = begin_size;
×
UNCOV
2781
                                     cindex < begin_size + 1;
×
2782
                                     cindex++)
2783
                                {
UNCOV
2784
                                    this->jlf_cached_line[cindex] = toupper(
×
UNCOV
2785
                                        this->jlf_cached_line[cindex]);
×
2786
                                }
UNCOV
2787
                                for (size_t cindex = begin_size + 1;
×
UNCOV
2788
                                     cindex < this->jlf_cached_line.size();
×
2789
                                     cindex++)
2790
                                {
UNCOV
2791
                                    this->jlf_cached_line[cindex] = tolower(
×
UNCOV
2792
                                        this->jlf_cached_line[cindex]);
×
2793
                                }
2794
                                break;
×
2795
                        }
2796
                        break;
10,464✔
2797
                }
2798
            }
2799
            this->json_append_to_cache("\n", 1);
1,485✔
2800
            sub_offset += 1;
1,485✔
2801

2802
            for (size_t lpc = 0; lpc < this->jlf_line_values.lvv_values.size();
11,021✔
2803
                 lpc++)
2804
            {
2805
                static const intern_string_t body_name
2806
                    = intern_string::lookup("body", -1);
9,536✔
2807
                auto& lv = this->jlf_line_values.lvv_values[lpc];
9,536✔
2808

2809
                if (lv.lv_meta.is_hidden() || used_values[lpc]
15,634✔
2810
                    || body_name == lv.lv_meta.lvm_name)
15,634✔
2811
                {
2812
                    continue;
8,759✔
2813
                }
2814

2815
                auto str = lv.to_string();
777✔
2816
                while (endswith(str, "\n")) {
777✔
UNCOV
2817
                    str.pop_back();
×
2818
                }
2819

2820
                lv.lv_sub_offset = sub_offset;
777✔
2821
                lv.lv_origin.lr_start = this->jlf_cached_line.size() + 2
777✔
2822
                    + lv.lv_meta.lvm_name.size() + 2;
777✔
2823
                auto frag = string_fragment::from_str(str);
777✔
2824
                while (true) {
2825
                    auto utf_scan_res = is_utf8(frag, '\n');
779✔
2826

2827
                    this->json_append_to_cache("  ", 2);
779✔
2828
                    this->json_append_to_cache(
779✔
2829
                        lv.lv_meta.lvm_name.to_string_fragment());
779✔
2830
                    this->json_append_to_cache(": ", 2);
779✔
2831
                    lr.lr_start = this->jlf_cached_line.size();
779✔
2832
                    this->json_append_to_cache(utf_scan_res.usr_valid_frag);
779✔
2833
                    lr.lr_end = this->jlf_cached_line.size();
779✔
2834
                    if (lv.lv_meta.lvm_name == this->elf_body_field) {
779✔
UNCOV
2835
                        this->jlf_line_attrs.emplace_back(lr, SA_BODY.value());
×
2836
                    } else {
2837
                        this->jlf_line_attrs.emplace_back(
779✔
2838
                            lr, SA_EXTRA_CONTENT.value());
1,558✔
2839
                    }
2840
                    this->json_append_to_cache("\n", 1);
779✔
2841
                    sub_offset += 1;
779✔
2842
                    if (utf_scan_res.usr_remaining) {
779✔
2843
                        frag = utf_scan_res.usr_remaining.value();
2✔
2844
                    } else {
2845
                        break;
777✔
2846
                    }
2847
                }
2✔
2848
                lv.lv_origin.lr_end = this->jlf_cached_line.size() - 1;
777✔
2849
                if (lv.lv_meta.lvm_name == this->elf_opid_field
777✔
2850
                    && !lv.lv_origin.empty())
777✔
2851
                {
UNCOV
2852
                    this->jlf_line_attrs.emplace_back(lv.lv_origin,
×
UNCOV
2853
                                                      L_OPID.value());
×
2854
                }
2855
            }
777✔
2856
        }
1,485✔
2857

2858
        this->jlf_line_offsets.push_back(0);
1,501✔
2859
        for (size_t lpc = 0; lpc < this->jlf_cached_line.size(); lpc++) {
156,652✔
2860
            if (this->jlf_cached_line[lpc] == '\n') {
155,151✔
2861
                this->jlf_line_offsets.push_back(lpc + 1);
3,066✔
2862
            }
2863
        }
2864
        this->jlf_line_offsets.push_back(this->jlf_cached_line.size());
1,501✔
2865
        this->jlf_cached_offset = ll.get_offset();
1,501✔
2866
        this->jlf_cached_opts = opts;
1,501✔
2867
    }
1,571✔
2868

2869
    off_t this_off = 0, next_off = 0;
3,319✔
2870

2871
    if (!this->jlf_line_offsets.empty()
3,319✔
2872
        && ll.get_sub_offset() < this->jlf_line_offsets.size())
3,319✔
2873
    {
2874
        require(ll.get_sub_offset() < this->jlf_line_offsets.size());
3,319✔
2875

2876
        this_off = this->jlf_line_offsets[ll.get_sub_offset()];
3,319✔
2877
        if ((ll.get_sub_offset() + 1) < (int) this->jlf_line_offsets.size()) {
3,319✔
2878
            next_off = this->jlf_line_offsets[ll.get_sub_offset() + 1];
3,319✔
2879
        } else {
UNCOV
2880
            next_off = this->jlf_cached_line.size();
×
2881
        }
2882
        if (next_off > 0 && this->jlf_cached_line[next_off - 1] == '\n'
3,319✔
2883
            && this_off != next_off)
6,638✔
2884
        {
2885
            next_off -= 1;
3,319✔
2886
        }
2887
    }
2888

2889
    if (opts.full_message) {
3,319✔
2890
        sbr.share(this->jlf_share_manager,
244✔
2891
                  &this->jlf_cached_line[0],
122✔
2892
                  this->jlf_cached_line.size());
2893
    } else {
2894
        sbr.share(this->jlf_share_manager,
6,394✔
2895
                  this->jlf_cached_line.data() + this_off,
3,197✔
2896
                  next_off - this_off);
3,197✔
2897
    }
2898
    sbr.get_metadata().m_valid_utf = ll.is_valid_utf();
3,319✔
2899
    sbr.get_metadata().m_has_ansi = ll.has_ansi();
3,319✔
2900
    this->jlf_cached_sub_range.lr_start = this_off;
3,319✔
2901
    this->jlf_cached_sub_range.lr_end = next_off;
3,319✔
2902
    this->jlf_line_values.lvv_sbr = sbr.clone();
3,319✔
2903
}
2904

2905
struct compiled_header_expr {
2906
    auto_mem<sqlite3_stmt> che_stmt{sqlite3_finalize};
2907
    bool che_enabled{true};
2908
};
2909

2910
struct format_header_expressions : public lnav_config_listener {
2911
    format_header_expressions() : lnav_config_listener(__FILE__) {}
1,091✔
2912

2913
    auto_sqlite3 e_db;
2914
    std::map<intern_string_t, std::map<std::string, compiled_header_expr>>
2915
        e_header_exprs;
2916
};
2917

2918
using safe_format_header_expressions = safe::Safe<format_header_expressions>;
2919

2920
static safe_format_header_expressions format_header_exprs;
2921

2922
std::optional<external_file_format>
2923
detect_mime_type(const std::filesystem::path& filename)
566✔
2924
{
2925
    uint8_t buffer[1024];
2926
    size_t buffer_size = 0;
566✔
2927

2928
    {
2929
        auto_fd fd;
566✔
2930

2931
        if ((fd = lnav::filesystem::openp(filename, O_RDONLY)) == -1) {
566✔
UNCOV
2932
            return std::nullopt;
×
2933
        }
2934

2935
        ssize_t rc;
2936

2937
        if ((rc = read(fd, buffer, sizeof(buffer))) == -1) {
566✔
UNCOV
2938
            return std::nullopt;
×
2939
        }
2940
        buffer_size = rc;
566✔
2941
    }
566✔
2942

2943
    auto hexbuf = auto_buffer::alloc(buffer_size * 2);
566✔
2944

2945
    for (size_t lpc = 0; lpc < buffer_size; lpc++) {
294,248✔
2946
        fmt::format_to(
293,682✔
2947
            std::back_inserter(hexbuf), FMT_STRING("{:02x}"), buffer[lpc]);
1,174,728✔
2948
    }
2949

2950
    safe::WriteAccess<safe_format_header_expressions> in(format_header_exprs);
566✔
2951

2952
    for (const auto& format : log_format::get_root_formats()) {
41,236✔
2953
        auto elf = std::dynamic_pointer_cast<external_log_format>(format);
40,670✔
2954
        if (elf == nullptr) {
40,670✔
2955
            continue;
2,830✔
2956
        }
2957

2958
        if (elf->elf_converter.c_header.h_exprs.he_exprs.empty()) {
37,840✔
2959
            continue;
37,274✔
2960
        }
2961

2962
        if (buffer_size < elf->elf_converter.c_header.h_size) {
566✔
2963
            log_debug(
19✔
2964
                "%s: file content too small (%d) for header detection: %s",
2965
                filename.c_str(),
2966
                buffer_size,
2967
                elf->get_name().get());
2968
            continue;
19✔
2969
        }
2970
        for (const auto& hpair : elf->elf_converter.c_header.h_exprs.he_exprs) {
2,735✔
2971
            auto& he = in->e_header_exprs[elf->get_name()][hpair.first];
2,188✔
2972

2973
            if (!he.che_enabled) {
2,188✔
UNCOV
2974
                continue;
×
2975
            }
2976

2977
            auto* stmt = he.che_stmt.in();
2,188✔
2978

2979
            if (stmt == nullptr) {
2,188✔
UNCOV
2980
                continue;
×
2981
            }
2982
            sqlite3_reset(stmt);
2,188✔
2983
            auto count = sqlite3_bind_parameter_count(stmt);
2,188✔
2984
            for (int lpc = 0; lpc < count; lpc++) {
4,376✔
2985
                const auto* name = sqlite3_bind_parameter_name(stmt, lpc + 1);
2,188✔
2986

2987
                if (name[0] == '$') {
2,188✔
2988
                    const char* env_value;
2989

UNCOV
2990
                    if ((env_value = getenv(&name[1])) != nullptr) {
×
UNCOV
2991
                        sqlite3_bind_text(
×
2992
                            stmt, lpc + 1, env_value, -1, SQLITE_STATIC);
2993
                    }
UNCOV
2994
                    continue;
×
2995
                }
2996
                if (strcmp(name, ":header") == 0) {
2,188✔
2997
                    sqlite3_bind_text(stmt,
2,188✔
2998
                                      lpc + 1,
2999
                                      hexbuf.in(),
2,188✔
3000
                                      hexbuf.size(),
2,188✔
3001
                                      SQLITE_STATIC);
3002
                    continue;
2,188✔
3003
                }
UNCOV
3004
                if (strcmp(name, ":filepath") == 0) {
×
UNCOV
3005
                    sqlite3_bind_text(
×
3006
                        stmt, lpc + 1, filename.c_str(), -1, SQLITE_STATIC);
UNCOV
3007
                    continue;
×
3008
                }
3009
            }
3010

3011
            auto step_res = sqlite3_step(stmt);
2,188✔
3012

3013
            switch (step_res) {
2,188✔
3014
                case SQLITE_OK:
2,188✔
3015
                case SQLITE_DONE:
3016
                    continue;
2,188✔
UNCOV
3017
                case SQLITE_ROW:
×
UNCOV
3018
                    break;
×
UNCOV
3019
                default: {
×
UNCOV
3020
                    log_error(
×
3021
                        "failed to execute file-format header expression: "
3022
                        "%s:%s -- %s",
3023
                        elf->get_name().get(),
3024
                        hpair.first.c_str(),
3025
                        sqlite3_errmsg(in->e_db));
3026
                    he.che_enabled = false;
×
UNCOV
3027
                    continue;
×
3028
                }
3029
            }
3030

3031
            log_info("detected format for: %s -- %s (header-expr: %s)",
×
3032
                     filename.c_str(),
3033
                     elf->get_name().get(),
3034
                     hpair.first.c_str());
UNCOV
3035
            return external_file_format{
×
3036
                elf->get_name().to_string(),
×
UNCOV
3037
                elf->elf_converter.c_command.pp_value,
×
3038
                elf->elf_converter.c_command.pp_location.sl_source.to_string(),
×
3039
            };
3040
        }
3041
    }
40,670✔
3042

3043
    return std::nullopt;
566✔
3044
}
566✔
3045

3046
log_format::scan_result_t
UNCOV
3047
log_format::test_line(sample_t& sample,
×
3048
                      std::vector<lnav::console::user_message>& msgs)
3049
{
UNCOV
3050
    return scan_no_match{};
×
3051
}
3052

3053
log_format::scan_result_t
3054
external_log_format::test_line(sample_t& sample,
165,007✔
3055
                               std::vector<lnav::console::user_message>& msgs)
3056
{
3057
    auto lines
3058
        = string_fragment::from_str(sample.s_line.pp_value).split_lines();
165,007✔
3059

3060
    if (this->elf_type == elf_type_t::ELF_TYPE_JSON) {
165,007✔
3061
        auto alloc = ArenaAlloc::Alloc<char>{};
2✔
3062
        auto sbc = scan_batch_context{
2✔
3063
            alloc,
3064
        };
2✔
3065
        std::vector<logline> dst;
2✔
3066
        auto li = line_info{
2✔
3067
            {0, lines[0].length()},
2✔
3068
        };
2✔
3069
        shared_buffer sb;
2✔
3070
        shared_buffer_ref sbr;
2✔
3071
        sbr.share(sb, lines[0].data(), (size_t) lines[0].length());
2✔
3072

3073
        return this->scan_json(dst, li, sbr, sbc);
2✔
3074
    }
2✔
3075

3076
    scan_result_t retval = scan_no_match{"no patterns matched"};
165,005✔
3077
    auto found = false;
165,005✔
3078

3079
    for (auto pat_iter = this->elf_pattern_order.begin();
165,005✔
3080
         pat_iter != this->elf_pattern_order.end();
1,247,750✔
3081
         ++pat_iter)
1,082,745✔
3082
    {
3083
        auto& pat = *(*pat_iter);
1,082,745✔
3084

3085
        if (!pat.p_pcre.pp_value) {
1,082,745✔
3086
            continue;
919,128✔
3087
        }
3088

3089
        auto md = pat.p_pcre.pp_value->create_match_data();
1,082,745✔
3090
        auto match_res = pat.p_pcre.pp_value->capture_from(lines[0])
1,082,745✔
3091
                             .into(md)
1,082,745✔
3092
                             .matches(PCRE2_NO_UTF_CHECK)
2,165,490✔
3093
                             .ignore_error();
1,082,745✔
3094
        if (!match_res) {
1,082,745✔
3095
            continue;
917,742✔
3096
        }
3097
        retval = scan_match{1000};
165,003✔
3098
        found = true;
165,003✔
3099

3100
        if (pat.p_module_format) {
165,003✔
3101
            continue;
1,386✔
3102
        }
3103

3104
        sample.s_matched_regexes.insert(pat.p_name.to_string());
163,617✔
3105

3106
        const auto ts_cap = md[pat.p_timestamp_field_index];
163,617✔
3107
        const auto level_cap = md[pat.p_level_field_index];
163,617✔
3108
        const char* const* custom_formats = this->get_timestamp_formats();
163,617✔
3109
        date_time_scanner dts;
163,617✔
3110
        timeval tv;
3111
        exttm tm;
163,617✔
3112

3113
        if (ts_cap && ts_cap->sf_begin == 0) {
163,617✔
3114
            pat.p_timestamp_end = ts_cap->sf_end;
102,252✔
3115
        }
3116
        const char* dts_scan_res = nullptr;
163,617✔
3117

3118
        if (ts_cap) {
163,617✔
3119
            dts_scan_res = dts.scan(
163,615✔
3120
                ts_cap->data(), ts_cap->length(), custom_formats, &tm, tv);
163,615✔
3121
        }
3122
        if (dts_scan_res != nullptr) {
163,617✔
3123
            if (dts_scan_res != ts_cap->data() + ts_cap->length()) {
163,614✔
UNCOV
3124
                auto match_len = dts_scan_res - ts_cap->data();
×
UNCOV
3125
                auto notes = attr_line_t("the used timestamp format: ");
×
UNCOV
3126
                if (custom_formats == nullptr) {
×
UNCOV
3127
                    notes.append(PTIMEC_FORMATS[dts.dts_fmt_lock].pf_fmt);
×
3128
                } else {
UNCOV
3129
                    notes.append(custom_formats[dts.dts_fmt_lock]);
×
3130
                }
UNCOV
3131
                notes.append("\n  ")
×
UNCOV
3132
                    .append(ts_cap.value())
×
UNCOV
3133
                    .append("\n")
×
UNCOV
3134
                    .append(2 + match_len, ' ')
×
UNCOV
3135
                    .append("^ matched up to here"_snippet_border);
×
UNCOV
3136
                auto um = lnav::console::user_message::warning(
×
UNCOV
3137
                              attr_line_t("timestamp was not fully matched: ")
×
UNCOV
3138
                                  .append_quoted(ts_cap.value()))
×
UNCOV
3139
                              .with_snippet(sample.s_line.to_snippet())
×
UNCOV
3140
                              .with_note(notes)
×
UNCOV
3141
                              .move();
×
3142

UNCOV
3143
                msgs.emplace_back(um);
×
3144
            }
3145
        } else if (!ts_cap) {
3✔
3146
            msgs.emplace_back(
2✔
UNCOV
3147
                lnav::console::user_message::error(
×
3148
                    attr_line_t("invalid sample log message: ")
4✔
3149
                        .append(lnav::to_json(sample.s_line.pp_value)))
4✔
3150
                    .with_reason(attr_line_t("timestamp was not captured"))
4✔
3151
                    .with_snippet(sample.s_line.to_snippet())
4✔
3152
                    .with_help(attr_line_t(
4✔
3153
                        "A timestamp needs to be captured in order for a "
3154
                        "line to be recognized as a log message")));
3155
        } else {
3156
            attr_line_t notes;
1✔
3157

3158
            if (custom_formats == nullptr) {
1✔
3159
                notes.append("the following built-in formats were tried:");
×
3160
                for (int lpc = 0; PTIMEC_FORMATS[lpc].pf_fmt != nullptr; lpc++)
×
3161
                {
UNCOV
3162
                    off_t off = 0;
×
3163

UNCOV
3164
                    PTIMEC_FORMATS[lpc].pf_func(
×
UNCOV
3165
                        &tm, ts_cap->data(), off, ts_cap->length());
×
UNCOV
3166
                    notes.append("\n  ")
×
UNCOV
3167
                        .append(ts_cap.value())
×
UNCOV
3168
                        .append("\n")
×
UNCOV
3169
                        .append(2 + off, ' ')
×
UNCOV
3170
                        .append("^ "_snippet_border)
×
3171
                        .append_quoted(
×
3172
                            lnav::roles::symbol(PTIMEC_FORMATS[lpc].pf_fmt))
×
3173
                        .append(" matched up to here"_snippet_border);
×
3174
                }
3175
            } else {
3176
                notes.append("the following custom formats were tried:");
1✔
3177
                for (int lpc = 0; custom_formats[lpc] != nullptr; lpc++) {
2✔
3178
                    off_t off = 0;
1✔
3179

3180
                    ptime_fmt(custom_formats[lpc],
1✔
3181
                              &tm,
3182
                              ts_cap->data(),
3183
                              off,
3184
                              ts_cap->length());
1✔
3185
                    notes.append("\n  ")
1✔
3186
                        .append(ts_cap.value())
1✔
3187
                        .append("\n")
1✔
3188
                        .append(2 + off, ' ')
1✔
3189
                        .append("^ "_snippet_border)
1✔
3190
                        .append_quoted(lnav::roles::symbol(custom_formats[lpc]))
2✔
3191
                        .append(" matched up to here"_snippet_border);
1✔
3192
                }
3193
            }
3194

3195
            msgs.emplace_back(
1✔
3196
                lnav::console::user_message::error(
×
3197
                    attr_line_t("invalid sample log message: ")
1✔
3198
                        .append(lnav::to_json(sample.s_line.pp_value)))
2✔
3199
                    .with_reason(attr_line_t("unrecognized timestamp -- ")
2✔
3200
                                     .append(ts_cap.value()))
1✔
3201
                    .with_snippet(sample.s_line.to_snippet())
2✔
3202
                    .with_note(notes)
1✔
3203
                    .with_help(attr_line_t("If the timestamp format is not "
2✔
3204
                                           "supported by default, you can "
3205
                                           "add a custom format with the ")
3206
                                   .append_quoted("timestamp-format"_symbol)
1✔
3207
                                   .append(" property")));
1✔
3208
        }
1✔
3209

3210
        auto level = this->convert_level(
163,617✔
3211
            level_cap.value_or(string_fragment::invalid()), nullptr);
163,617✔
3212

3213
        if (sample.s_level != LEVEL_UNKNOWN && sample.s_level != level) {
163,617✔
3214
            attr_line_t note_al;
1✔
3215

3216
            note_al.append("matched regex = ")
1✔
3217
                .append(lnav::roles::symbol(pat.p_name.to_string()))
2✔
3218
                .append("\n")
1✔
3219
                .append("captured level = ")
1✔
3220
                .append_quoted(level_cap->to_string());
1✔
3221
            if (level_cap && !this->elf_level_patterns.empty()) {
1✔
3222
                thread_local auto md = lnav::pcre2pp::match_data::unitialized();
1✔
3223

3224
                note_al.append("\nlevel regular expression match results:");
1✔
3225
                for (const auto& level_pattern : this->elf_level_patterns) {
3✔
3226
                    attr_line_t regex_al
3227
                        = level_pattern.second.lp_pcre.pp_value->get_pattern();
2✔
3228
                    lnav::snippets::regex_highlighter(
2✔
3229
                        regex_al, -1, line_range{0, (int) regex_al.length()});
2✔
3230
                    note_al.append("\n  ")
2✔
3231
                        .append(lnav::roles::symbol(
4✔
3232
                            level_pattern.second.lp_pcre.pp_path.to_string()))
4✔
3233
                        .append(" = ")
2✔
3234
                        .append(regex_al)
2✔
3235
                        .append("\n    ");
2✔
3236
                    auto match_res = level_pattern.second.lp_pcre.pp_value
2✔
3237
                                         ->capture_from(level_cap.value())
2✔
3238
                                         .into(md)
2✔
3239
                                         .matches(PCRE2_NO_UTF_CHECK)
4✔
3240
                                         .ignore_error();
2✔
3241
                    if (!match_res) {
2✔
3242
                        note_al.append(lnav::roles::warning("no match"));
1✔
3243
                        continue;
1✔
3244
                    }
3245

3246
                    note_al.append(level_cap.value())
1✔
3247
                        .append("\n    ")
1✔
3248
                        .append(md.leading().length(), ' ')
1✔
3249
                        .append("^"_snippet_border);
1✔
3250
                    if (match_res->f_all.length() > 2) {
1✔
3251
                        note_al.append(lnav::roles::snippet_border(
1✔
3252
                            std::string(match_res->f_all.length() - 2, '-')));
3✔
3253
                    }
3254
                    if (match_res->f_all.length() > 1) {
1✔
3255
                        note_al.append("^"_snippet_border);
1✔
3256
                    }
3257
                }
2✔
3258
            }
3259
            auto um
UNCOV
3260
                = lnav::console::user_message::error(
×
3261
                      attr_line_t("invalid sample log message: ")
1✔
3262
                          .append(lnav::to_json(sample.s_line.pp_value)))
2✔
3263
                      .with_reason(attr_line_t()
2✔
3264
                                       .append_quoted(lnav::roles::symbol(
2✔
3265
                                           level_names[level]))
1✔
3266
                                       .append(" does not match the expected "
1✔
3267
                                               "level of ")
3268
                                       .append_quoted(lnav::roles::symbol(
2✔
3269
                                           level_names[sample.s_level])))
1✔
3270
                      .with_snippet(sample.s_line.to_snippet())
2✔
3271
                      .with_note(note_al)
1✔
3272
                      .move();
1✔
3273
            if (!this->elf_level_patterns.empty()) {
1✔
3274
                um.with_help(
1✔
3275
                    attr_line_t("Level regexes are not anchored to the "
2✔
3276
                                "start/end of the string.  Prepend ")
3277
                        .append_quoted("^"_symbol)
1✔
3278
                        .append(" to the expression to match from the "
1✔
3279
                                "start of the string and append ")
3280
                        .append_quoted("$"_symbol)
1✔
3281
                        .append(" to match up to the end of the string."));
1✔
3282
            }
3283
            msgs.emplace_back(um);
1✔
3284
        }
1✔
3285

3286
        {
3287
            auto full_match_res
3288
                = pat.p_pcre.pp_value->capture_from(sample.s_line.pp_value)
163,617✔
3289
                      .into(md)
163,617✔
3290
                      .matches()
327,234✔
3291
                      .ignore_error();
163,617✔
3292
            if (!full_match_res) {
163,617✔
3293
                attr_line_t regex_al = pat.p_pcre.pp_value->get_pattern();
1✔
3294
                lnav::snippets::regex_highlighter(
1✔
3295
                    regex_al, -1, line_range{0, (int) regex_al.length()});
1✔
3296
                msgs.emplace_back(
1✔
3297
                    lnav::console::user_message::error(
×
3298
                        attr_line_t("invalid pattern: ")
1✔
3299
                            .append_quoted(
1✔
3300
                                lnav::roles::symbol(pat.p_name.to_string())))
2✔
3301
                        .with_reason("pattern does not match entire "
2✔
3302
                                     "multiline sample message")
3303
                        .with_snippet(sample.s_line.to_snippet())
2✔
3304
                        .with_note(attr_line_t()
2✔
3305
                                       .append(lnav::roles::symbol(
1✔
3306
                                           pat.p_name.to_string()))
2✔
3307
                                       .append(" = ")
1✔
3308
                                       .append(regex_al))
1✔
3309
                        .with_help(
3310
                            attr_line_t("use ").append_quoted(".*").append(
2✔
3311
                                " to match new-lines")));
3312
            } else if (static_cast<size_t>(full_match_res->f_all.length())
163,617✔
3313
                       != sample.s_line.pp_value.length())
163,616✔
3314
            {
3315
                attr_line_t regex_al = pat.p_pcre.pp_value->get_pattern();
1✔
3316
                lnav::snippets::regex_highlighter(
1✔
3317
                    regex_al, -1, line_range{0, (int) regex_al.length()});
1✔
3318
                auto match_length
3319
                    = static_cast<size_t>(full_match_res->f_all.length());
1✔
3320
                attr_line_t sample_al = sample.s_line.pp_value;
1✔
3321
                sample_al.append("\n")
1✔
3322
                    .append(match_length, ' ')
1✔
3323
                    .append("^ matched up to here"_error)
1✔
3324
                    .with_attr_for_all(VC_ROLE.value(role_t::VCR_QUOTED_CODE));
1✔
3325
                auto sample_snippet = lnav::console::snippet::from(
3326
                    sample.s_line.pp_location, sample_al);
1✔
3327
                msgs.emplace_back(
1✔
3328
                    lnav::console::user_message::error(
×
3329
                        attr_line_t("invalid pattern: ")
1✔
3330
                            .append_quoted(
1✔
3331
                                lnav::roles::symbol(pat.p_name.to_string())))
2✔
3332
                        .with_reason("pattern does not match entire "
2✔
3333
                                     "message")
3334
                        .with_snippet(sample_snippet)
1✔
3335
                        .with_note(attr_line_t()
3✔
3336
                                       .append(lnav::roles::symbol(
2✔
3337
                                           pat.p_name.to_string()))
2✔
3338
                                       .append(" = ")
1✔
3339
                                       .append(regex_al))
1✔
3340
                        .with_help("update the regular expression to fully "
3341
                                   "capture the sample message"));
3342
            }
1✔
3343
        }
3344
    }
1,082,745✔
3345

3346
    if (!found && !this->elf_pattern_order.empty()) {
165,005✔
3347
        std::vector<std::pair<ssize_t, intern_string_t>> partial_indexes;
2✔
3348
        attr_line_t notes;
2✔
3349
        size_t max_name_width = 0;
2✔
3350

3351
        for (const auto& pat_iter : this->elf_pattern_order) {
11✔
3352
            auto& pat = *pat_iter;
9✔
3353

3354
            if (!pat.p_pcre.pp_value) {
9✔
UNCOV
3355
                continue;
×
3356
            }
3357

3358
            partial_indexes.emplace_back(
9✔
3359
                pat.p_pcre.pp_value->match_partial(lines[0]), pat.p_name);
9✔
3360
            max_name_width = std::max(max_name_width, pat.p_name.size());
9✔
3361
        }
3362
        for (const auto& line_frag : lines) {
4✔
3363
            auto src_line = attr_line_t(line_frag.to_string());
2✔
3364
            if (!line_frag.endswith("\n")) {
2✔
3365
                src_line.append("\n");
2✔
3366
            }
3367
            src_line.with_attr_for_all(VC_ROLE.value(role_t::VCR_QUOTED_CODE));
2✔
3368
            notes.append("   ").append(src_line);
2✔
3369
            for (auto& part_pair : partial_indexes) {
11✔
3370
                if (part_pair.first >= 0
18✔
3371
                    && part_pair.first < line_frag.length())
9✔
3372
                {
3373
                    notes.append("   ")
9✔
3374
                        .append(part_pair.first, ' ')
9✔
3375
                        .append("^ "_snippet_border)
9✔
3376
                        .append(
9✔
3377
                            lnav::roles::symbol(part_pair.second.to_string()))
18✔
3378
                        .append(" matched up to here"_snippet_border)
9✔
3379
                        .append("\n");
9✔
3380
                }
3381
                part_pair.first -= line_frag.length();
9✔
3382
            }
3383
        }
2✔
3384
        notes.add_header(
2✔
3385
            "the following shows how each pattern matched this sample:\n");
3386

3387
        attr_line_t regex_note;
2✔
3388
        for (const auto& pat_iter : this->elf_pattern_order) {
11✔
3389
            if (!pat_iter->p_pcre.pp_value) {
9✔
3390
                regex_note
3391
                    .append(lnav::roles::symbol(fmt::format(
×
3392
                        FMT_STRING("{:{}}"), pat_iter->p_name, max_name_width)))
×
3393
                    .append(" is invalid");
×
3394
                continue;
×
3395
            }
3396

3397
            attr_line_t regex_al = pat_iter->p_pcre.pp_value->get_pattern();
9✔
3398
            lnav::snippets::regex_highlighter(
9✔
3399
                regex_al, -1, line_range{0, (int) regex_al.length()});
9✔
3400

3401
            regex_note
3402
                .append(lnav::roles::symbol(fmt::format(
18✔
3403
                    FMT_STRING("{:{}}"), pat_iter->p_name, max_name_width)))
27✔
3404
                .append(" = ")
9✔
3405
                .append_quoted(regex_al)
18✔
3406
                .append("\n");
9✔
3407
        }
9✔
3408

3409
        msgs.emplace_back(
2✔
3410
            lnav::console::user_message::error(
×
3411
                attr_line_t("invalid sample log message: ")
2✔
3412
                    .append(lnav::to_json(sample.s_line.pp_value)))
4✔
3413
                .with_reason("sample does not match any patterns")
4✔
3414
                .with_snippet(sample.s_line.to_snippet())
4✔
3415
                .with_note(notes.rtrim())
4✔
3416
                .with_note(regex_note));
3417
    }
2✔
3418

3419
    return retval;
165,005✔
3420
}
165,007✔
3421

3422
void
3423
external_log_format::build(std::vector<lnav::console::user_message>& errors)
46,782✔
3424
{
3425
    auto& vc = view_colors::singleton();
46,782✔
3426

3427
    if (!this->lf_timestamp_field.empty()) {
46,782✔
3428
        auto& vd = this->elf_value_defs[this->lf_timestamp_field];
46,782✔
3429
        if (vd.get() == nullptr) {
46,782✔
3430
            vd = std::make_shared<value_def>(
34,214✔
3431
                this->lf_timestamp_field,
34,214✔
3432
                value_kind_t::VALUE_TEXT,
×
UNCOV
3433
                logline_value_meta::internal_column{},
×
3434
                this);
34,214✔
3435
            if (this->elf_type == elf_type_t::ELF_TYPE_JSON) {
34,214✔
3436
                this->elf_value_def_order.emplace_back(vd);
4,630✔
3437
            }
3438
        }
3439
        vd->vd_meta.lvm_name = this->lf_timestamp_field;
46,782✔
3440
        vd->vd_meta.lvm_kind = value_kind_t::VALUE_TEXT;
46,782✔
3441
        vd->vd_meta.lvm_column = logline_value_meta::internal_column{};
46,782✔
3442
        vd->vd_internal = true;
46,782✔
3443

3444
        this->elf_value_defs[LOG_TIME_STR] = vd;
46,782✔
3445
    }
3446

3447
    if (!this->lf_subsecond_field.empty()) {
46,782✔
3448
        if (!this->lf_subsecond_unit.has_value()) {
95✔
3449
            errors.emplace_back(
1✔
3450
                lnav::console::user_message::error(
×
3451
                    attr_line_t()
2✔
3452
                        .append_quoted(
1✔
3453
                            lnav::roles::symbol(this->elf_name.to_string()))
2✔
3454
                        .append(" is not a valid log format"))
1✔
3455
                    .with_reason(attr_line_t()
2✔
3456
                                     .append_quoted("subsecond-units"_symbol)
1✔
3457
                                     .append(" must be set when ")
1✔
3458
                                     .append_quoted("subsecond-field"_symbol)
1✔
3459
                                     .append(" is used"))
1✔
3460
                    .with_snippets(this->get_snippets()));
2✔
3461
        } else {
3462
            auto& vd = this->elf_value_defs[this->lf_subsecond_field];
94✔
3463
            if (vd.get() == nullptr) {
94✔
3464
                vd = std::make_shared<value_def>(
94✔
3465
                    this->lf_subsecond_field,
94✔
3466
                    value_kind_t::VALUE_INTEGER,
×
UNCOV
3467
                    logline_value_meta::internal_column{},
×
3468
                    this);
94✔
3469
                if (this->elf_type == elf_type_t::ELF_TYPE_JSON) {
94✔
3470
                    this->elf_value_def_order.emplace_back(vd);
94✔
3471
                }
3472
            }
3473
            vd->vd_meta.lvm_name = this->lf_subsecond_field;
94✔
3474
            vd->vd_meta.lvm_column = logline_value_meta::internal_column{};
94✔
3475
            vd->vd_meta.lvm_hidden = true;
94✔
3476
            vd->vd_internal = true;
94✔
3477
        }
3478
    }
3479

3480
    if (startswith(this->elf_level_field.get(), "/")) {
46,782✔
3481
        this->elf_level_field
3482
            = intern_string::lookup(this->elf_level_field.get() + 1);
188✔
3483
    }
3484
    if (!this->elf_level_field.empty()) {
46,782✔
3485
        auto level_iter = this->elf_value_defs.find(this->elf_level_field);
46,782✔
3486
        if (level_iter == this->elf_value_defs.end()) {
46,782✔
3487
            auto& vd = this->elf_value_defs[this->elf_level_field];
23,724✔
3488
            vd = std::make_shared<value_def>(
23,724✔
3489
                this->elf_level_field,
23,724✔
3490
                value_kind_t::VALUE_TEXT,
×
3491
                logline_value_meta::internal_column{},
×
3492
                this);
23,724✔
3493
            if (this->elf_type == elf_type_t::ELF_TYPE_JSON) {
23,724✔
3494
                this->elf_value_def_order.emplace_back(vd);
2,457✔
3495
            }
3496
            vd->vd_meta.lvm_name = this->elf_level_field;
23,724✔
3497
            vd->vd_meta.lvm_column = logline_value_meta::internal_column{};
23,724✔
3498
            vd->vd_internal = true;
23,724✔
3499

3500
            if (this->elf_level_field != this->elf_body_field) {
23,724✔
3501
                this->elf_value_defs[LOG_LEVEL_STR] = vd;
22,937✔
3502
            }
3503
        } else {
3504
            if (level_iter->second->vd_meta.lvm_kind
23,058✔
3505
                != value_kind_t::VALUE_TEXT)
23,058✔
3506
            {
3507
                this->lf_level_hideable = false;
4,945✔
3508
            }
3509
            this->elf_value_defs[LOG_LEVEL_STR] = level_iter->second;
23,058✔
3510
        }
3511
    }
3512

3513
    auto opid_field_iter = this->elf_value_defs.find(LOG_OPID_STR);
46,782✔
3514
    if (opid_field_iter == this->elf_value_defs.end()) {
46,782✔
3515
        auto vd
3516
            = std::make_shared<value_def>(this->elf_opid_field,
46,782✔
3517
                                          value_kind_t::VALUE_TEXT,
×
3518
                                          logline_value_meta::internal_column{},
×
3519
                                          this);
46,782✔
3520
        if (this->elf_type == elf_type_t::ELF_TYPE_JSON) {
46,782✔
3521
            this->elf_value_def_order.emplace_back(vd);
7,496✔
3522
        }
3523
        vd->vd_meta.lvm_name = LOG_OPID_STR;
46,782✔
3524
        vd->vd_meta.lvm_kind = value_kind_t::VALUE_TEXT;
46,782✔
3525
        vd->vd_meta.lvm_column = logline_value_meta::internal_column{};
46,782✔
3526
        vd->vd_internal = true;
46,782✔
3527

3528
        this->elf_value_defs[LOG_OPID_STR] = vd;
46,782✔
3529
    }
46,782✔
3530

3531
    if (!this->elf_body_field.empty()) {
46,782✔
3532
        auto& vd = this->elf_value_defs[this->elf_body_field];
46,782✔
3533
        if (vd.get() == nullptr) {
46,782✔
3534
            vd = std::make_shared<value_def>(
38,277✔
3535
                this->elf_body_field,
38,277✔
3536
                value_kind_t::VALUE_TEXT,
×
3537
                logline_value_meta::internal_column{},
×
3538
                this);
38,277✔
3539
            if (this->elf_type == elf_type_t::ELF_TYPE_JSON) {
38,277✔
3540
                this->elf_value_def_order.emplace_back(vd);
4,629✔
3541
            }
3542
        }
3543
        vd->vd_meta.lvm_name = this->elf_body_field;
46,782✔
3544
        vd->vd_meta.lvm_kind = value_kind_t::VALUE_TEXT;
46,782✔
3545
        vd->vd_meta.lvm_column = logline_value_meta::internal_column{};
46,782✔
3546
        vd->vd_internal = true;
46,782✔
3547
    }
3548

3549
    if (!this->elf_src_file_field.empty()) {
46,782✔
3550
        auto& vd = this->elf_value_defs[this->elf_src_file_field];
6,930✔
3551
        if (vd.get() == nullptr) {
6,930✔
NEW
3552
            vd = std::make_shared<value_def>(
×
NEW
3553
                this->elf_src_file_field,
×
NEW
3554
                value_kind_t::VALUE_TEXT,
×
NEW
3555
                logline_value_meta::internal_column{},
×
NEW
3556
                this);
×
3557
        }
3558
        vd->vd_meta.lvm_name = this->elf_src_file_field;
6,930✔
3559
        vd->vd_meta.lvm_kind = value_kind_t::VALUE_TEXT;
6,930✔
3560
        vd->vd_meta.lvm_column = logline_value_meta::internal_column{};
6,930✔
3561
    }
3562

3563
    if (!this->elf_src_line_field.empty()) {
46,782✔
3564
        auto& vd = this->elf_value_defs[this->elf_src_line_field];
6,930✔
3565
        if (vd.get() == nullptr) {
6,930✔
NEW
3566
            vd = std::make_shared<value_def>(
×
NEW
3567
                this->elf_src_line_field,
×
NEW
3568
                value_kind_t::VALUE_INTEGER,
×
NEW
3569
                logline_value_meta::internal_column{},
×
NEW
3570
                this);
×
3571
        }
3572
        vd->vd_meta.lvm_name = this->elf_src_line_field;
6,930✔
3573
        vd->vd_meta.lvm_kind = value_kind_t::VALUE_INTEGER;
6,930✔
3574
        vd->vd_meta.lvm_column = logline_value_meta::internal_column{};
6,930✔
3575
    }
3576

3577
    if (!this->lf_timestamp_format.empty()) {
46,782✔
3578
        this->lf_timestamp_format.push_back(nullptr);
4,949✔
3579
    }
3580
    for (auto iter = this->elf_patterns.begin();
46,782✔
3581
         iter != this->elf_patterns.end();
137,448✔
3582
         ++iter)
90,666✔
3583
    {
3584
        pattern& pat = *iter->second;
90,666✔
3585

3586
        if (pat.p_pcre.pp_value == nullptr) {
90,666✔
3587
            continue;
1✔
3588
        }
3589

3590
        if (pat.p_module_format) {
90,665✔
3591
            this->elf_has_module_format = true;
2,079✔
3592
        }
3593

3594
        for (auto named_cap : pat.p_pcre.pp_value->get_named_captures()) {
775,866✔
3595
            const intern_string_t name
3596
                = intern_string::lookup(named_cap.get_name());
685,201✔
3597

3598
            if (name == this->lf_timestamp_field) {
685,201✔
3599
                pat.p_timestamp_field_index = named_cap.get_index();
88,584✔
3600
            }
3601
            if (name == this->lf_time_field) {
685,201✔
3602
                pat.p_time_field_index = named_cap.get_index();
693✔
3603
            }
3604
            if (name == this->elf_level_field) {
685,201✔
3605
                pat.p_level_field_index = named_cap.get_index();
68,985✔
3606
            }
3607
            if (name == this->elf_module_id_field) {
685,201✔
3608
                pat.p_module_field_index = named_cap.get_index();
5,545✔
3609
            }
3610
            if (name == this->elf_opid_field) {
685,201✔
3611
                pat.p_opid_field_index = named_cap.get_index();
23,562✔
3612
            }
3613
            if (name == this->elf_subid_field) {
685,201✔
3614
                pat.p_subid_field_index = named_cap.get_index();
10,395✔
3615
            }
3616
            if (name == this->elf_body_field) {
685,201✔
3617
                pat.p_body_field_index = named_cap.get_index();
77,496✔
3618
            }
3619
            if (name == this->elf_src_file_field) {
685,201✔
3620
                pat.p_src_file_field_index = named_cap.get_index();
11,088✔
3621
            }
3622
            if (name == this->elf_src_line_field) {
685,201✔
3623
                pat.p_src_line_field_index = named_cap.get_index();
12,474✔
3624
            }
3625

3626
            auto value_iter = this->elf_value_defs.find(name);
685,201✔
3627
            if (value_iter != this->elf_value_defs.end()) {
685,201✔
3628
                auto vd = value_iter->second;
673,324✔
3629
                indexed_value_def ivd;
673,324✔
3630

3631
                ivd.ivd_index = named_cap.get_index();
673,324✔
3632
                if (!vd->vd_unit_field.empty()) {
673,324✔
3633
                    ivd.ivd_unit_field_index = pat.p_pcre.pp_value->name_index(
1,386✔
3634
                        vd->vd_unit_field.get());
693✔
3635
                } else {
3636
                    ivd.ivd_unit_field_index = -1;
672,631✔
3637
                }
3638
                if (!vd->vd_internal
673,324✔
3639
                    && !vd->vd_meta.lvm_column
1,157,416✔
3640
                            .is<logline_value_meta::table_column>())
484,092✔
3641
                {
3642
                    vd->vd_meta.lvm_column = logline_value_meta::table_column{
268,569✔
3643
                        this->elf_column_count++};
268,569✔
3644
                }
3645
                ivd.ivd_value_def = vd;
673,324✔
3646
                pat.p_value_by_index.push_back(ivd);
673,324✔
3647
            }
673,324✔
3648
            pat.p_value_name_to_index[name] = named_cap.get_index();
685,201✔
3649
        }
3650

3651
        stable_sort(pat.p_value_by_index.begin(), pat.p_value_by_index.end());
90,665✔
3652

3653
        for (int lpc = 0; lpc < (int) pat.p_value_by_index.size(); lpc++) {
763,989✔
3654
            auto& ivd = pat.p_value_by_index[lpc];
673,324✔
3655
            auto vd = ivd.ivd_value_def;
673,324✔
3656

3657
            if (!vd->vd_meta.lvm_foreign_key && !vd->vd_meta.lvm_identifier) {
673,324✔
3658
                switch (vd->vd_meta.lvm_kind) {
343,960✔
3659
                    case value_kind_t::VALUE_INTEGER:
44,446✔
3660
                    case value_kind_t::VALUE_FLOAT:
3661
                        pat.p_numeric_value_indexes.push_back(lpc);
44,446✔
3662
                        break;
44,446✔
3663
                    default:
299,514✔
3664
                        break;
299,514✔
3665
                }
3666
            }
3667
        }
673,324✔
3668

3669
        if (!pat.p_module_format && pat.p_timestamp_field_index == -1) {
90,665✔
3670
            errors.emplace_back(
2✔
3671
                lnav::console::user_message::error(
×
3672
                    attr_line_t("invalid pattern: ")
4✔
3673
                        .append_quoted(lnav::roles::symbol(pat.p_config_path)))
4✔
3674
                    .with_reason("no timestamp capture found in the pattern")
4✔
3675
                    .with_snippets(this->get_snippets())
4✔
3676
                    .with_help("all log messages need a timestamp"));
3677
        }
3678

3679
        if (!this->elf_level_field.empty() && pat.p_level_field_index == -1) {
90,665✔
3680
            log_warning("%s:level field '%s' not found in pattern",
21,680✔
3681
                        pat.p_config_path.c_str(),
3682
                        this->elf_level_field.get());
3683
        }
3684
        if (!this->elf_module_id_field.empty()
90,665✔
3685
            && pat.p_module_field_index == -1)
90,665✔
3686
        {
3687
            log_warning("%s:module field '%s' not found in pattern",
×
3688
                        pat.p_config_path.c_str(),
3689
                        this->elf_module_id_field.get());
3690
        }
3691
        if (!this->elf_body_field.empty() && pat.p_body_field_index == -1) {
90,665✔
3692
            log_warning("%s:body field '%s' not found in pattern",
13,169✔
3693
                        pat.p_config_path.c_str(),
3694
                        this->elf_body_field.get());
3695
        }
3696

3697
        this->elf_pattern_order.push_back(iter->second);
90,665✔
3698
    }
3699

3700
    if (this->elf_type != elf_type_t::ELF_TYPE_TEXT) {
46,782✔
3701
        if (!this->elf_patterns.empty()) {
7,496✔
3702
            errors.emplace_back(
1✔
UNCOV
3703
                lnav::console::user_message::error(
×
3704
                    attr_line_t()
2✔
3705
                        .append_quoted(
1✔
3706
                            lnav::roles::symbol(this->elf_name.to_string()))
2✔
3707
                        .append(" is not a valid log format"))
1✔
3708
                    .with_reason("structured logs cannot have regexes")
2✔
3709
                    .with_snippets(this->get_snippets()));
2✔
3710
        }
3711
        if (this->elf_type == elf_type_t::ELF_TYPE_JSON) {
7,496✔
3712
            this->lf_multiline = true;
7,496✔
3713
            this->lf_structured = true;
7,496✔
3714
            this->lf_formatted_lines = true;
7,496✔
3715
            this->jlf_parse_context
3716
                = std::make_shared<yajlpp_parse_context>(this->elf_name);
7,496✔
3717
            this->jlf_yajl_handle.reset(
7,496✔
3718
                yajl_alloc(&this->jlf_parse_context->ypc_callbacks,
7,496✔
3719
                           nullptr,
3720
                           this->jlf_parse_context.get()),
7,496✔
3721
                yajl_handle_deleter());
3722
            yajl_config(
7,496✔
3723
                this->jlf_yajl_handle.get(), yajl_dont_validate_strings, 1);
3724
        }
3725
    } else {
3726
        if (this->elf_patterns.empty()) {
39,286✔
3727
            errors.emplace_back(lnav::console::user_message::error(
2✔
3728
                                    attr_line_t()
4✔
3729
                                        .append_quoted(lnav::roles::symbol(
4✔
3730
                                            this->elf_name.to_string()))
4✔
3731
                                        .append(" is not a valid log format"))
2✔
3732
                                    .with_reason("no regexes specified")
4✔
3733
                                    .with_snippets(this->get_snippets()));
4✔
3734
        }
3735
    }
3736

3737
    stable_sort(this->elf_level_pairs.begin(), this->elf_level_pairs.end());
46,782✔
3738

3739
    {
3740
        safe::WriteAccess<safe_format_header_expressions> hexprs(
3741
            format_header_exprs);
46,782✔
3742

3743
        if (hexprs->e_db.in() == nullptr) {
46,782✔
3744
            if (sqlite3_open(":memory:", hexprs->e_db.out()) != SQLITE_OK) {
693✔
UNCOV
3745
                log_error("unable to open memory DB");
×
UNCOV
3746
                return;
×
3747
            }
3748
            register_sqlite_funcs(hexprs->e_db.in(), sqlite_registration_funcs);
693✔
3749
        }
3750

3751
        for (const auto& hpair : this->elf_converter.c_header.h_exprs.he_exprs)
49,555✔
3752
        {
3753
            auto stmt_str
3754
                = fmt::format(FMT_STRING("SELECT 1 WHERE {}"), hpair.second);
8,319✔
3755
            compiled_header_expr che;
2,773✔
3756

3757
            log_info("preparing file-format header expression: %s",
2,773✔
3758
                     stmt_str.c_str());
3759
            auto retcode = sqlite3_prepare_v2(hexprs->e_db.in(),
5,546✔
3760
                                              stmt_str.c_str(),
3761
                                              stmt_str.size(),
2,773✔
3762
                                              che.che_stmt.out(),
3763
                                              nullptr);
3764
            if (retcode != SQLITE_OK) {
2,773✔
3765
                auto sql_al = attr_line_t(hpair.second)
2✔
3766
                                  .with_attr_for_all(SA_PREFORMATTED.value())
2✔
3767
                                  .with_attr_for_all(
1✔
3768
                                      VC_ROLE.value(role_t::VCR_QUOTED_CODE))
2✔
3769
                                  .move();
1✔
3770
                readline_sql_highlighter(
1✔
3771
                    sql_al, lnav::sql::dialect::sqlite, std::nullopt);
3772
                intern_string_t watch_expr_path = intern_string::lookup(
3773
                    fmt::format(FMT_STRING("/{}/converter/header/expr/{}"),
3✔
3774
                                this->elf_name,
1✔
3775
                                hpair.first));
2✔
3776
                auto snippet = lnav::console::snippet::from(
3777
                    source_location(watch_expr_path), sql_al);
1✔
3778

3779
                auto um = lnav::console::user_message::error(
2✔
3780
                              "SQL expression is invalid")
3781
                              .with_reason(sqlite3_errmsg(hexprs->e_db.in()))
2✔
3782
                              .with_snippet(snippet)
1✔
3783
                              .move();
1✔
3784

3785
                errors.emplace_back(um);
1✔
3786
                continue;
1✔
3787
            }
1✔
3788

3789
            hexprs->e_header_exprs[this->elf_name][hpair.first]
2,772✔
3790
                = std::move(che);
5,544✔
3791
        }
2,774✔
3792

3793
        if (!this->elf_converter.c_header.h_exprs.he_exprs.empty()
46,782✔
3794
            && this->elf_converter.c_command.pp_value.empty())
46,782✔
3795
        {
3796
            auto um = lnav::console::user_message::error(
2✔
3797
                          "A command is required when a converter is defined")
3798
                          .with_help(
2✔
3799
                              "The converter command transforms the file "
3800
                              "into a format that can be consumed by lnav")
3801
                          .with_snippets(this->get_snippets())
2✔
3802
                          .move();
1✔
3803
            errors.emplace_back(um);
1✔
3804
        }
1✔
3805
    }
46,782✔
3806

3807
    for (auto& vd : this->elf_value_def_order) {
454,821✔
3808
        std::vector<std::string>::iterator act_iter;
408,039✔
3809

3810
        if (!vd->vd_internal
408,039✔
3811
            && log_vtab_impl::RESERVED_COLUMNS.count(
776,486✔
3812
                vd->vd_meta.lvm_name.to_string_fragment()))
776,486✔
3813
        {
UNCOV
3814
            auto um = lnav::console::user_message::error(
×
3815
                          attr_line_t("value name ")
1✔
3816
                              .append_quoted(lnav::roles::symbol(
2✔
3817
                                  fmt::format(FMT_STRING("/{}/value/{}"),
4✔
3818
                                              this->elf_name,
1✔
3819
                                              vd->vd_meta.lvm_name)))
1✔
3820
                              .append(" is reserved and cannot be used"))
1✔
3821
                          .with_reason(
2✔
3822
                              "lnav automatically defines several columns in "
3823
                              "the log virtual table")
3824
                          .with_snippets(this->get_snippets())
2✔
3825
                          .with_help("Choose another name")
2✔
3826
                          .move();
1✔
3827
            errors.emplace_back(um);
1✔
3828
        }
1✔
3829

3830
        vd->vd_meta.lvm_format = this;
408,039✔
3831
        if (!vd->vd_internal
408,039✔
3832
            && !vd->vd_meta.lvm_column.is<logline_value_meta::table_column>())
408,039✔
3833
        {
3834
            vd->vd_meta.lvm_column
99,878✔
3835
                = logline_value_meta::table_column{this->elf_column_count++};
99,878✔
3836
        }
3837

3838
        if (vd->vd_meta.lvm_kind == value_kind_t::VALUE_UNKNOWN) {
408,039✔
3839
            vd->vd_meta.lvm_kind = value_kind_t::VALUE_TEXT;
×
3840
        }
3841

3842
        if (this->elf_type == elf_type_t::ELF_TYPE_TEXT) {
408,039✔
3843
            std::set<std::string> available_captures;
283,124✔
3844

3845
            bool found_in_pattern = false;
283,124✔
3846
            for (const auto& pat : this->elf_patterns) {
486,870✔
3847
                if (pat.second->p_pcre.pp_value == nullptr) {
486,868✔
UNCOV
3848
                    continue;
×
3849
                }
3850

3851
                auto cap_index = pat.second->p_pcre.pp_value->name_index(
973,736✔
3852
                    vd->vd_meta.lvm_name.get());
486,868✔
3853
                if (cap_index >= 0) {
486,868✔
3854
                    found_in_pattern = true;
283,122✔
3855
                    break;
283,122✔
3856
                }
3857

3858
                for (auto named_cap :
203,746✔
3859
                     pat.second->p_pcre.pp_value->get_named_captures())
1,825,380✔
3860
                {
3861
                    available_captures.insert(named_cap.get_name().to_string());
1,417,888✔
3862
                }
3863
            }
3864
            if (!found_in_pattern) {
283,124✔
3865
                auto notes
3866
                    = attr_line_t("the following captures are available:\n  ")
2✔
3867
                          .join(available_captures,
2✔
3868
                                VC_ROLE.value(role_t::VCR_SYMBOL),
4✔
3869
                                ", ")
3870
                          .move();
2✔
3871
                errors.emplace_back(
2✔
UNCOV
3872
                    lnav::console::user_message::warning(
×
3873
                        attr_line_t("invalid value ")
2✔
3874
                            .append_quoted(lnav::roles::symbol(
4✔
3875
                                fmt::format(FMT_STRING("/{}/value/{}"),
8✔
3876
                                            this->elf_name,
2✔
3877
                                            vd->vd_meta.lvm_name.get()))))
4✔
3878
                        .with_reason(
4✔
3879
                            attr_line_t("no patterns have a capture named ")
4✔
3880
                                .append_quoted(vd->vd_meta.lvm_name.get()))
2✔
3881
                        .with_note(notes)
2✔
3882
                        .with_snippets(this->get_snippets())
4✔
3883
                        .with_help("values are populated from captures in "
3884
                                   "patterns, so at least one pattern must "
3885
                                   "have a capture with this value name"));
3886
            }
2✔
3887
        }
283,124✔
3888

3889
        for (act_iter = vd->vd_action_list.begin();
408,039✔
3890
             act_iter != vd->vd_action_list.end();
408,732✔
3891
             ++act_iter)
693✔
3892
        {
3893
            if (this->lf_action_defs.find(*act_iter)
693✔
3894
                == this->lf_action_defs.end())
1,386✔
3895
            {
3896
#if 0
3897
                errors.push_back("error:" + this->elf_name.to_string() + ":"
3898
                                 + vd->vd_meta.lvm_name.get()
3899
                                 + ": cannot find action -- " + (*act_iter));
3900
#endif
3901
            }
3902
        }
3903

3904
        vd->set_rewrite_src_name();
408,039✔
3905
    }
3906

3907
    if (this->elf_type == elf_type_t::ELF_TYPE_JSON) {
46,782✔
3908
        for (const auto& vd : this->elf_value_def_order) {
132,411✔
3909
            this->elf_value_def_frag_map[vd->vd_meta.lvm_name
124,915✔
3910
                                             .to_string_fragment()]
124,915✔
3911
                = vd.get();
249,830✔
3912
        }
3913
    }
3914

3915
    for (const auto& td_pair : this->lf_tag_defs) {
48,958✔
3916
        const auto& td = td_pair.second;
2,176✔
3917

3918
        if (td->ftd_pattern.pp_value == nullptr
2,176✔
3919
            || td->ftd_pattern.pp_value->get_pattern().empty())
2,176✔
3920
        {
3921
            errors.emplace_back(
3✔
UNCOV
3922
                lnav::console::user_message::error(
×
3923
                    attr_line_t("invalid tag definition ")
6✔
3924
                        .append_quoted(lnav::roles::symbol(
6✔
3925
                            fmt::format(FMT_STRING("/{}/tags/{}"),
12✔
3926
                                        this->elf_name,
3✔
3927
                                        td_pair.first))))
3✔
3928
                    .with_reason(
6✔
3929
                        "tag definitions must have a non-empty pattern")
3930
                    .with_snippets(this->get_snippets()));
6✔
3931
        }
3932
    }
3933

3934
    for (const auto& opid_desc_pair : *this->lf_opid_description_def) {
56,484✔
3935
        for (const auto& opid_desc : *opid_desc_pair.second.od_descriptors) {
22,869✔
3936
            auto iter = this->elf_value_defs.find(opid_desc.od_field.pp_value);
13,167✔
3937
            if (iter == this->elf_value_defs.end()) {
13,167✔
UNCOV
3938
                errors.emplace_back(
×
UNCOV
3939
                    lnav::console::user_message::error(
×
UNCOV
3940
                        attr_line_t("invalid opid description field ")
×
UNCOV
3941
                            .append_quoted(lnav::roles::symbol(
×
UNCOV
3942
                                opid_desc.od_field.pp_path.to_string())))
×
UNCOV
3943
                        .with_reason(
×
UNCOV
3944
                            attr_line_t("unknown value name ")
×
UNCOV
3945
                                .append_quoted(opid_desc.od_field.pp_value))
×
UNCOV
3946
                        .with_snippets(this->get_snippets()));
×
3947
            } else {
3948
                this->lf_desc_fields.insert(iter->first);
13,167✔
3949
                iter->second->vd_is_desc_field = true;
13,167✔
3950
            }
3951
        }
3952
    }
3953

3954
    for (const auto& subid_desc_pair : *this->lf_subid_description_def) {
47,475✔
3955
        for (const auto& subid_desc : *subid_desc_pair.second.od_descriptors) {
1,386✔
3956
            auto iter = this->elf_value_defs.find(subid_desc.od_field.pp_value);
693✔
3957
            if (iter == this->elf_value_defs.end()) {
693✔
UNCOV
3958
                errors.emplace_back(
×
UNCOV
3959
                    lnav::console::user_message::error(
×
UNCOV
3960
                        attr_line_t("invalid subid description field ")
×
UNCOV
3961
                            .append_quoted(lnav::roles::symbol(
×
UNCOV
3962
                                subid_desc.od_field.pp_path.to_string())))
×
UNCOV
3963
                        .with_reason(
×
UNCOV
3964
                            attr_line_t("unknown value name ")
×
UNCOV
3965
                                .append_quoted(subid_desc.od_field.pp_value))
×
UNCOV
3966
                        .with_snippets(this->get_snippets()));
×
3967
            } else {
3968
                this->lf_desc_fields.insert(iter->first);
693✔
3969
                iter->second->vd_is_desc_field = true;
693✔
3970
            }
3971
        }
3972
    }
3973

3974
    if (this->elf_type == elf_type_t::ELF_TYPE_TEXT
93,564✔
3975
        && this->elf_samples.empty())
46,782✔
3976
    {
3977
        errors.emplace_back(
3✔
UNCOV
3978
            lnav::console::user_message::error(
×
3979
                attr_line_t()
6✔
3980
                    .append_quoted(
3✔
3981
                        lnav::roles::symbol(this->elf_name.to_string()))
6✔
3982
                    .append(" is not a valid log format"))
3✔
3983
                .with_reason("log message samples must be included in a format "
6✔
3984
                             "definition")
3985
                .with_snippets(this->get_snippets()));
6✔
3986
    }
3987

3988
    for (const auto& pat : this->elf_pattern_order) {
137,447✔
3989
        if (pat->p_module_format || this->elf_type != elf_type_t::ELF_TYPE_TEXT)
90,665✔
3990
        {
3991
            continue;
2,080✔
3992
        }
3993
        if (pat->p_pcre.pp_value->name_index(this->lf_timestamp_field.get())
88,585✔
3994
            < 0)
88,585✔
3995
        {
3996
            attr_line_t notes;
1✔
3997
            bool first_note = true;
1✔
3998

3999
            if (pat->p_pcre.pp_value->get_capture_count() > 0) {
1✔
4000
                notes.append("the following captures are available:\n  ");
1✔
4001
            }
4002
            for (auto named_cap : pat->p_pcre.pp_value->get_named_captures()) {
4✔
4003
                if (!first_note) {
3✔
4004
                    notes.append(", ");
2✔
4005
                }
4006
                notes.append(
3✔
4007
                    lnav::roles::symbol(named_cap.get_name().to_string()));
6✔
4008
                first_note = false;
3✔
4009
            }
4010
            errors.emplace_back(
1✔
UNCOV
4011
                lnav::console::user_message::error(
×
4012
                    attr_line_t("invalid value for property ")
1✔
4013
                        .append_quoted(lnav::roles::symbol(
2✔
4014
                            fmt::format(FMT_STRING("/{}/timestamp-field"),
4✔
4015
                                        this->elf_name))))
1✔
4016
                    .with_reason(
2✔
4017
                        attr_line_t()
2✔
4018
                            .append_quoted(this->lf_timestamp_field)
1✔
4019
                            .append(" was not found in the pattern at ")
1✔
4020
                            .append(lnav::roles::symbol(pat->p_config_path)))
2✔
4021
                    .with_note(notes)
1✔
4022
                    .with_snippets(this->get_snippets()));
2✔
4023
        }
1✔
4024
    }
4025

4026
    for (size_t sample_index = 0; sample_index < this->elf_samples.size();
211,785✔
4027
         sample_index += 1)
165,003✔
4028
    {
4029
        auto& elf_sample = this->elf_samples[sample_index];
165,003✔
4030
        auto sample_lines
4031
            = string_fragment(elf_sample.s_line.pp_value).split_lines();
165,003✔
4032

4033
        if (this->test_line(elf_sample, errors).is<scan_match>()) {
165,003✔
4034
            for (const auto& pat_name : elf_sample.s_matched_regexes) {
328,618✔
4035
                this->elf_patterns[pat_name]->p_matched_samples.emplace(
163,616✔
4036
                    sample_index);
4037
            }
4038
        }
4039
    }
165,003✔
4040

4041
    if (!this->elf_samples.empty()) {
46,782✔
4042
        for (const auto& elf_sample : this->elf_samples) {
204,286✔
4043
            if (elf_sample.s_matched_regexes.size() <= 1) {
165,003✔
4044
                continue;
165,003✔
4045
            }
4046

UNCOV
4047
            errors.emplace_back(
×
UNCOV
4048
                lnav::console::user_message::warning(
×
UNCOV
4049
                    attr_line_t("invalid log format: ")
×
UNCOV
4050
                        .append_quoted(
×
UNCOV
4051
                            lnav::roles::symbol(this->elf_name.to_string())))
×
UNCOV
4052
                    .with_reason(
×
UNCOV
4053
                        attr_line_t(
×
4054
                            "sample is matched by more than one regex: ")
UNCOV
4055
                            .join(elf_sample.s_matched_regexes,
×
UNCOV
4056
                                  VC_ROLE.value(role_t::VCR_SYMBOL),
×
4057
                                  ", "))
UNCOV
4058
                    .with_snippet(lnav::console::snippet::from(
×
4059
                        elf_sample.s_line.pp_location,
UNCOV
4060
                        attr_line_t().append(lnav::roles::quoted_code(
×
UNCOV
4061
                            elf_sample.s_line.pp_value))))
×
4062
                    .with_help("log format regexes must match a single type "
4063
                               "of log message"));
4064
        }
4065

4066
        for (const auto& pat : this->elf_pattern_order) {
129,945✔
4067
            if (pat->p_module_format) {
90,662✔
4068
                continue;
2,079✔
4069
            }
4070

4071
            if (pat->p_matched_samples.empty()) {
88,583✔
4072
                errors.emplace_back(
2✔
UNCOV
4073
                    lnav::console::user_message::warning(
×
4074
                        attr_line_t("invalid pattern: ")
4✔
4075
                            .append_quoted(
2✔
4076
                                lnav::roles::symbol(pat->p_config_path)))
4✔
4077
                        .with_reason("pattern does not match any samples")
4✔
4078
                        .with_snippet(lnav::console::snippet::from(
6✔
4079
                            pat->p_pcre.pp_location, ""))
2✔
4080
                        .with_help(
4081
                            "every pattern should have at least one sample "
4082
                            "that it matches"));
4083
            }
4084
        }
4085
    }
4086

4087
    size_t value_def_index = 0;
46,782✔
4088
    for (auto& elf_value_def : this->elf_value_def_order) {
454,821✔
4089
        elf_value_def->vd_meta.lvm_values_index
408,039✔
4090
            = std::make_optional(value_def_index++);
408,039✔
4091

4092
        if (elf_value_def->vd_meta.lvm_foreign_key
408,039✔
4093
            || elf_value_def->vd_meta.lvm_identifier)
408,039✔
4094
        {
4095
            continue;
216,053✔
4096
        }
4097

4098
        switch (elf_value_def->vd_meta.lvm_kind) {
191,986✔
4099
            case value_kind_t::VALUE_INTEGER:
48,886✔
4100
            case value_kind_t::VALUE_FLOAT:
4101
                this->elf_numeric_value_defs.push_back(elf_value_def);
48,886✔
4102
                break;
48,886✔
4103
            default:
143,100✔
4104
                break;
143,100✔
4105
        }
4106
    }
4107

4108
    int format_index = 0;
46,782✔
4109
    for (auto iter = this->jlf_line_format.begin();
46,782✔
4110
         iter != this->jlf_line_format.end();
138,744✔
4111
         ++iter, format_index++)
91,962✔
4112
    {
4113
        static const intern_string_t ts
4114
            = intern_string::lookup("__timestamp__");
93,348✔
4115
        static const intern_string_t level_field
4116
            = intern_string::lookup("__level__");
93,348✔
4117
        auto& jfe = *iter;
91,962✔
4118

4119
        if (startswith(jfe.jfe_value.pp_value.get(), "/")) {
91,962✔
4120
            jfe.jfe_value.pp_value
4121
                = intern_string::lookup(jfe.jfe_value.pp_value.get() + 1);
188✔
4122
        }
4123
        if (!jfe.jfe_ts_format.empty()) {
91,962✔
4124
            if (!jfe.jfe_value.pp_value.empty() && jfe.jfe_value.pp_value != ts)
787✔
4125
            {
4126
                log_warning(
94✔
4127
                    "%s:line-format[%d]:ignoring field '%s' since "
4128
                    "timestamp-format was used",
4129
                    this->elf_name.get(),
4130
                    format_index,
4131
                    jfe.jfe_value.pp_value.get());
4132
            }
4133
            jfe.jfe_value.pp_value = ts;
787✔
4134
        }
4135

4136
        switch (jfe.jfe_type) {
91,962✔
4137
            case json_log_field::VARIABLE: {
60,471✔
4138
                auto vd_iter
4139
                    = this->elf_value_defs.find(jfe.jfe_value.pp_value);
60,471✔
4140
                if (jfe.jfe_value.pp_value == ts) {
60,471✔
4141
                    this->elf_value_defs[this->lf_timestamp_field]
5,827✔
4142
                        ->vd_meta.lvm_hidden
5,827✔
4143
                        = true;
5,827✔
4144
                } else if (jfe.jfe_value.pp_value == level_field) {
54,644✔
4145
                    this->elf_value_defs[this->elf_level_field]
2,772✔
4146
                        ->vd_meta.lvm_hidden
2,772✔
4147
                        = true;
2,772✔
4148
                } else if (vd_iter == this->elf_value_defs.end()) {
51,872✔
4149
                    errors.emplace_back(
2✔
UNCOV
4150
                        lnav::console::user_message::error(
×
4151
                            attr_line_t("invalid line format element ")
4✔
4152
                                .append_quoted(lnav::roles::symbol(fmt::format(
4✔
4153
                                    FMT_STRING("/{}/line-format/{}/field"),
6✔
4154
                                    this->elf_name,
2✔
4155
                                    format_index))))
4156
                            .with_reason(
4✔
4157
                                attr_line_t()
4✔
4158
                                    .append_quoted(jfe.jfe_value.pp_value)
2✔
4159
                                    .append(" is not a defined value"))
2✔
4160
                            .with_snippet(jfe.jfe_value.to_snippet()));
4✔
4161
                } else {
4162
                    vd_iter->second->vd_line_format_index = format_index;
51,870✔
4163
                    switch (vd_iter->second->vd_meta.lvm_kind) {
51,870✔
4164
                        case value_kind_t::VALUE_INTEGER:
10,583✔
4165
                        case value_kind_t::VALUE_FLOAT:
4166
                            if (jfe.jfe_align
10,583✔
4167
                                == json_format_element::align_t::NONE)
4168
                            {
4169
                                jfe.jfe_align
4170
                                    = json_format_element::align_t::RIGHT;
9,890✔
4171
                            }
4172
                            break;
10,583✔
4173
                        default:
41,287✔
4174
                            break;
41,287✔
4175
                    }
4176
                }
4177
                break;
60,471✔
4178
            }
4179
            case json_log_field::CONSTANT:
31,491✔
4180
                this->jlf_line_format_init_count
31,491✔
4181
                    += std::count(jfe.jfe_default_value.begin(),
31,491✔
4182
                                  jfe.jfe_default_value.end(),
4183
                                  '\n');
31,491✔
4184
                break;
31,491✔
UNCOV
4185
            default:
×
UNCOV
4186
                break;
×
4187
        }
4188
    }
4189

4190
    for (auto& hd_pair : this->elf_highlighter_patterns) {
47,160✔
4191
        auto& hd = hd_pair.second;
378✔
4192
        text_attrs attrs;
378✔
4193

4194
        if (!hd.hd_color.pp_value.empty()) {
378✔
4195
            attrs.ta_fg_color = vc.match_color(
754✔
4196
                styling::color_unit::from_str(hd.hd_color.pp_value)
754✔
4197
                    .unwrapOrElse([&](const auto& msg) {
1,131✔
4198
                        errors.emplace_back(
1✔
4199
                            lnav::console::user_message::error(
4200
                                attr_line_t()
1✔
4201
                                    .append_quoted(hd.hd_color.pp_value)
2✔
4202
                                    .append(" is not a valid color value for "
1✔
4203
                                            "property ")
4204
                                    .append_quoted(lnav::roles::symbol(
2✔
4205
                                        hd.hd_color.pp_path.to_string())))
1✔
4206
                                .with_reason(msg)
2✔
4207
                                .with_snippet(hd.hd_color.to_snippet()));
1✔
4208
                        return styling::color_unit::EMPTY;
1✔
4209
                    }));
377✔
4210
        }
4211

4212
        if (!hd.hd_background_color.pp_value.empty()) {
378✔
4213
            attrs.ta_bg_color = vc.match_color(
2✔
4214
                styling::color_unit::from_str(hd.hd_background_color.pp_value)
2✔
4215
                    .unwrapOrElse([&](const auto& msg) {
3✔
4216
                        errors.emplace_back(
1✔
4217
                            lnav::console::user_message::error(
4218
                                attr_line_t()
1✔
4219
                                    .append_quoted(
2✔
4220
                                        hd.hd_background_color.pp_value)
1✔
4221
                                    .append(" is not a valid color value for "
1✔
4222
                                            "property ")
4223
                                    .append_quoted(lnav::roles::symbol(
2✔
4224
                                        hd.hd_background_color.pp_path
1✔
4225
                                            .to_string())))
4226
                                .with_reason(msg)
2✔
4227
                                .with_snippet(
4228
                                    hd.hd_background_color.to_snippet()));
1✔
4229
                        return styling::color_unit::EMPTY;
1✔
4230
                    }));
1✔
4231
        }
4232

4233
        if (hd.hd_underline) {
378✔
4234
            attrs |= text_attrs::style::underline;
94✔
4235
        }
4236
        if (hd.hd_blink) {
378✔
UNCOV
4237
            attrs |= text_attrs::style::blink;
×
4238
        }
4239

4240
        if (hd.hd_pattern.pp_value != nullptr) {
378✔
4241
            this->lf_highlighters.emplace_back(hd.hd_pattern.pp_value);
376✔
4242
            this->lf_highlighters.back()
376✔
4243
                .with_name(hd_pair.first.to_string())
752✔
4244
                .with_format_name(this->elf_name)
376✔
4245
                .with_attrs(attrs);
376✔
4246
        }
4247
    }
378✔
4248

4249
    this->lf_value_stats.resize(this->elf_value_defs.size());
46,782✔
4250
}
4251

4252
void
4253
external_log_format::register_vtabs(
38,752✔
4254
    log_vtab_manager* vtab_manager,
4255
    std::vector<lnav::console::user_message>& errors)
4256
{
4257
    for (auto& elf_search_table : this->elf_search_tables) {
45,701✔
4258
        if (elf_search_table.second.std_pattern.pp_value == nullptr) {
6,949✔
4259
            continue;
1✔
4260
        }
4261

4262
        auto lst = std::make_shared<log_search_table>(
4263
            elf_search_table.second.std_pattern.pp_value,
6,948✔
4264
            elf_search_table.first);
6,948✔
4265
        lst->lst_format = this;
6,948✔
4266
        lst->lst_log_path_glob = elf_search_table.second.std_glob;
6,948✔
4267
        if (elf_search_table.second.std_level != LEVEL_UNKNOWN) {
6,948✔
4268
            lst->lst_log_level = elf_search_table.second.std_level;
2,895✔
4269
        }
4270
        auto errmsg = vtab_manager->register_vtab(lst);
6,948✔
4271
        if (!errmsg.empty()) {
6,948✔
4272
#if 0
4273
            errors.push_back("error:" + this->elf_name.to_string() + ":"
4274
                             + search_iter->first.to_string()
4275
                             + ":unable to register table -- " + errmsg);
4276
#endif
4277
        }
4278
    }
6,948✔
4279
}
38,752✔
4280

4281
bool
4282
external_log_format::match_samples(const std::vector<sample_t>& samples) const
3,121,176✔
4283
{
4284
    for (const auto& sample_iter : samples) {
13,981,427✔
4285
        for (const auto& pat_iter : this->elf_pattern_order) {
30,618,401✔
4286
            auto& pat = *pat_iter;
19,758,150✔
4287

4288
            if (!pat.p_pcre.pp_value) {
19,758,150✔
UNCOV
4289
                continue;
×
4290
            }
4291

4292
            if (pat.p_pcre.pp_value
39,516,300✔
4293
                    ->find_in(sample_iter.s_line.pp_value, PCRE2_NO_UTF_CHECK)
39,516,300✔
4294
                    .ignore_error())
39,516,300✔
4295
            {
4296
                return true;
15,102✔
4297
            }
4298
        }
4299
    }
4300

4301
    return false;
3,106,074✔
4302
}
4303

4304
class external_log_table : public log_format_vtab_impl {
4305
public:
4306
    explicit external_log_table(const external_log_format& elf)
38,752✔
4307
        : log_format_vtab_impl(elf), elt_format(elf)
38,752✔
4308
    {
4309
    }
38,752✔
4310

4311
    void get_columns(std::vector<vtab_column>& cols) const override
39,119✔
4312
    {
4313
        const auto& elf = this->elt_format;
39,119✔
4314

4315
        cols.resize(elf.elf_column_count);
39,119✔
4316
        for (const auto& vd : elf.elf_value_def_order) {
382,264✔
4317
            auto type_pair = log_vtab_impl::logline_value_to_sqlite_type(
343,145✔
4318
                vd->vd_meta.lvm_kind);
343,145✔
4319

4320
            if (!vd->vd_meta.lvm_column.is<logline_value_meta::table_column>())
343,145✔
4321
            {
4322
                continue;
32,489✔
4323
            }
4324

4325
            auto col
4326
                = vd->vd_meta.lvm_column.get<logline_value_meta::table_column>()
310,656✔
4327
                      .value;
310,656✔
4328
            require(0 <= col && col < elf.elf_column_count);
310,656✔
4329

4330
            cols[col].vc_name = vd->vd_meta.lvm_name.get();
310,656✔
4331
            cols[col].vc_type = type_pair.first;
310,656✔
4332
            cols[col].vc_subtype = type_pair.second;
310,656✔
4333
            cols[col].vc_collator = vd->vd_collate;
310,656✔
4334
            cols[col].vc_comment = vd->vd_description;
310,656✔
4335
        }
4336
    }
39,119✔
4337

4338
    void get_foreign_keys(
31,669✔
4339
        std::unordered_set<std::string>& keys_inout) const override
4340
    {
4341
        log_vtab_impl::get_foreign_keys(keys_inout);
31,669✔
4342

4343
        for (const auto& elf_value_def : this->elt_format.elf_value_defs) {
455,507✔
4344
            if (elf_value_def.second->vd_meta.lvm_foreign_key
423,838✔
4345
                || elf_value_def.second->vd_meta.lvm_identifier)
423,838✔
4346
            {
4347
                keys_inout.emplace(elf_value_def.first.to_string());
152,217✔
4348
            }
4349
        }
4350
    }
31,669✔
4351

4352
    bool next(log_cursor& lc, logfile_sub_source& lss) override
3,468✔
4353
    {
4354
        if (lc.is_eof()) {
3,468✔
UNCOV
4355
            return true;
×
4356
        }
4357

4358
        content_line_t cl(lss.at(lc.lc_curr_line));
3,468✔
4359
        auto* lf = lss.find_file_ptr(cl);
3,468✔
4360
        auto lf_iter = lf->begin() + cl;
3,468✔
4361
        uint8_t mod_id = lf_iter->get_module_id();
3,468✔
4362

4363
        if (lf_iter->is_continued()) {
3,468✔
UNCOV
4364
            return false;
×
4365
        }
4366

4367
        this->elt_module_format.mf_mod_format = nullptr;
3,468✔
4368
        if (lf->get_format_name() == this->lfvi_format.get_name()) {
3,468✔
4369
            return true;
3,457✔
4370
        }
4371
        if (mod_id && mod_id == this->lfvi_format.lf_mod_index) {
11✔
4372
            auto format = lf->get_format();
2✔
4373

4374
            return lf->read_line(lf_iter)
2✔
4375
                .map([this, format, cl, lf](auto line) {
4✔
4376
                    string_attrs_t sa;
2✔
4377
                    logline_value_vector values;
2✔
4378
                    line_range mod_name_range;
2✔
4379
                    intern_string_t mod_name;
2✔
4380

4381
                    values.lvv_sbr = line.clone();
2✔
4382
                    format->annotate(lf, cl, sa, values, false);
2✔
4383
                    this->elt_container_body
2✔
4384
                        = find_string_attr_range(sa, &SA_BODY);
2✔
4385
                    if (!this->elt_container_body.is_valid()) {
2✔
UNCOV
4386
                        return false;
×
4387
                    }
4388
                    this->elt_container_body.ltrim(line.get_data());
2✔
4389
                    mod_name_range = find_string_attr_range(sa, &L_MODULE);
2✔
4390
                    if (!mod_name_range.is_valid()) {
2✔
UNCOV
4391
                        return false;
×
4392
                    }
4393
                    mod_name = intern_string::lookup(
2✔
4394
                        &line.get_data()[mod_name_range.lr_start],
2✔
4395
                        mod_name_range.length());
2✔
4396
                    this->elt_module_format
2✔
4397
                        = external_log_format::MODULE_FORMATS[mod_name];
2✔
4398
                    if (!this->elt_module_format.mf_mod_format) {
2✔
UNCOV
4399
                        return false;
×
4400
                    }
4401
                    return this->elt_module_format.mf_mod_format->get_name()
2✔
4402
                        == this->lfvi_format.get_name();
4✔
4403
                })
2✔
4404
                .unwrapOr(false);
2✔
4405
        }
2✔
4406

4407
        return false;
9✔
4408
    }
4409

4410
    void extract(logfile* lf,
3,374✔
4411
                 uint64_t line_number,
4412
                 string_attrs_t& sa,
4413
                 logline_value_vector& values) override
4414
    {
4415
        auto& line = values.lvv_sbr;
3,374✔
4416
        auto format = lf->get_format();
3,374✔
4417

4418
        if (this->elt_module_format.mf_mod_format != nullptr) {
3,374✔
4419
            shared_buffer_ref body_ref;
2✔
4420

4421
            body_ref.subset(line,
2✔
4422
                            this->elt_container_body.lr_start,
2✔
4423
                            this->elt_container_body.length());
2✔
4424
            sa.clear();
2✔
4425
            auto narrow_res
4426
                = values.lvv_sbr.narrow(this->elt_container_body.lr_start,
2✔
4427
                                        this->elt_container_body.length());
2✔
4428
            values.lvv_values.clear();
2✔
4429
            this->elt_module_format.mf_mod_format->annotate(
2✔
4430
                lf, line_number, sa, values, false);
4431
            values.lvv_sbr.widen(narrow_res);
2✔
4432
        } else {
2✔
4433
            sa.clear();
3,372✔
4434
            format->annotate(lf, line_number, sa, values, false);
3,372✔
4435
        }
4436
    }
3,374✔
4437

4438
    const external_log_format& elt_format;
4439
    module_format elt_module_format;
4440
    struct line_range elt_container_body;
4441
};
4442

4443
std::shared_ptr<log_vtab_impl>
4444
external_log_format::get_vtab_impl() const
38,752✔
4445
{
4446
    return std::make_shared<external_log_table>(*this);
38,752✔
4447
}
4448

4449
std::shared_ptr<log_format>
4450
external_log_format::specialized(int fmt_lock)
517✔
4451
{
4452
    auto retval = std::make_shared<external_log_format>(*this);
517✔
4453

4454
    retval->lf_specialized = true;
517✔
4455
    this->lf_pattern_locks.clear();
517✔
4456
    if (fmt_lock != -1) {
517✔
4457
        retval->lf_pattern_locks.emplace_back(0, fmt_lock);
41✔
4458
    }
4459

4460
    if (this->elf_type == elf_type_t::ELF_TYPE_JSON) {
517✔
4461
        this->jlf_parse_context
4462
            = std::make_shared<yajlpp_parse_context>(this->elf_name);
37✔
4463
        this->jlf_yajl_handle.reset(
37✔
4464
            yajl_alloc(&this->jlf_parse_context->ypc_callbacks,
37✔
4465
                       nullptr,
4466
                       this->jlf_parse_context.get()),
37✔
4467
            yajl_handle_deleter());
4468
        yajl_config(this->jlf_yajl_handle.get(), yajl_dont_validate_strings, 1);
37✔
4469
        this->jlf_cached_line.reserve(16 * 1024);
37✔
4470
    }
4471

4472
    this->lf_value_stats.clear();
517✔
4473
    this->lf_value_stats.resize(this->elf_value_defs.size());
517✔
4474
    this->elf_specialized_value_defs_state = *this->elf_value_defs_state;
517✔
4475

4476
    return retval;
1,034✔
4477
}
517✔
4478

4479
log_format::match_name_result
4480
external_log_format::match_name(const std::string& filename)
681,751✔
4481
{
4482
    if (this->elf_filename_pcre.pp_value == nullptr) {
681,751✔
4483
        return name_matched{};
680,425✔
4484
    }
4485

4486
    if (this->elf_filename_pcre.pp_value->find_in(filename)
2,652✔
4487
            .ignore_error()
2,652✔
4488
            .has_value())
1,326✔
4489
    {
4490
        return name_matched{};
238✔
4491
    }
4492

4493
    return name_mismatched{
2,176✔
4494
        this->elf_filename_pcre.pp_value->match_partial(filename),
2,176✔
4495
        this->elf_filename_pcre.pp_value->get_pattern(),
1,088✔
4496
    };
1,088✔
4497
}
4498

4499
auto
4500
external_log_format::value_line_count(const value_def* vd,
64,685✔
4501
                                      bool top_level,
4502
                                      std::optional<double> val,
4503
                                      const unsigned char* str,
4504
                                      ssize_t len,
4505
                                      yajl_string_props_t* props)
4506
    -> value_line_count_result
4507
{
4508
    value_line_count_result retval;
64,685✔
4509

4510
    if (vd == nullptr) {
64,685✔
4511
        if (this->jlf_hide_extra || !top_level) {
60,494✔
4512
            retval.vlcr_count = 0;
52,521✔
4513
        }
4514

4515
        return retval;
60,494✔
4516
    }
4517

4518
    if (str != nullptr && props != nullptr && !val) {
4,191✔
4519
        auto frag = string_fragment::from_bytes(str, len);
2,521✔
4520
        while (frag.endswith("\n")) {
2,690✔
4521
            frag.pop_back();
169✔
4522
            props->line_feeds -= 1;
169✔
4523
        }
4524
        retval.vlcr_has_ansi |= props->has_ansi;
2,521✔
4525
        retval.vlcr_count += props->line_feeds;
2,521✔
4526
    }
4527

4528
    if (vd->vd_meta.lvm_values_index) {
4,191✔
4529
        auto& lvs = this->lf_value_stats[vd->vd_meta.lvm_values_index.value()];
4,191✔
4530
        if (len > lvs.lvs_width) {
4,191✔
4531
            lvs.lvs_width = len;
616✔
4532
        }
4533
        if (val) {
4,191✔
4534
            lvs.add_value(val.value());
103✔
4535
        }
4536
    }
4537

4538
    if (vd->vd_line_format_index) {
4,191✔
4539
        retval.vlcr_line_format_count += 1;
1,934✔
4540
        retval.vlcr_count -= 1;
1,934✔
4541
        retval.vlcr_line_format_index = vd->vd_line_format_index;
1,934✔
4542
    } else if (vd->vd_meta.is_hidden()) {
2,257✔
4543
        retval.vlcr_count = 0;
2,106✔
4544
        return retval;
2,106✔
4545
    }
4546

4547
    return retval;
2,085✔
4548
}
4549

4550
log_level_t
4551
external_log_format::convert_level(string_fragment sf,
169,715✔
4552
                                   scan_batch_context* sbc) const
4553
{
4554
    auto retval = LEVEL_INFO;
169,715✔
4555

4556
    if (sf.is_valid()) {
169,715✔
4557
        if (sbc != nullptr) {
133,986✔
4558
            auto ssm_res = sbc->sbc_level_cache.lookup(sf);
5,905✔
4559
            if (ssm_res.has_value()) {
5,905✔
4560
                return static_cast<log_level_t>(ssm_res.value());
3,965✔
4561
            }
4562
        }
4563

4564
        if (this->elf_level_patterns.empty()) {
130,021✔
4565
            retval = string2level(sf.data(), sf.length());
25,289✔
4566
        } else {
4567
            for (const auto& elf_level_pattern : this->elf_level_patterns) {
282,677✔
4568
                if (elf_level_pattern.second.lp_pcre.pp_value
518,652✔
4569
                        ->find_in(sf, PCRE2_NO_UTF_CHECK)
518,652✔
4570
                        .ignore_error()
518,652✔
4571
                        .has_value())
259,326✔
4572
                {
4573
                    retval = elf_level_pattern.first;
81,381✔
4574
                    break;
81,381✔
4575
                }
4576
            }
4577
        }
4578

4579
        if (sbc != nullptr
130,021✔
4580
            && sf.length() <= lnav::small_string_map::MAX_KEY_SIZE)
130,021✔
4581
        {
4582
            sbc->sbc_level_cache.insert(sf, retval);
1,302✔
4583
        }
4584
    }
4585

4586
    return retval;
165,750✔
4587
}
4588

4589
logline_value_meta
4590
external_log_format::get_value_meta(intern_string_t field_name,
7,335✔
4591
                                    value_kind_t kind)
4592
{
4593
    const auto iter = this->elf_value_defs.find(field_name);
7,335✔
4594
    if (iter == this->elf_value_defs.end()) {
7,335✔
4595
        auto retval = logline_value_meta(
4596
            field_name, kind, logline_value_meta::external_column{}, this);
721✔
4597

4598
        retval.lvm_hidden = this->jlf_hide_extra;
721✔
4599
        return retval;
721✔
4600
    }
721✔
4601

4602
    auto lvm = iter->second->vd_meta;
6,614✔
4603

4604
    lvm.lvm_kind = kind;
6,614✔
4605
    return lvm;
6,614✔
4606
}
6,614✔
4607

4608
logline_value_meta
4609
external_log_format::get_value_meta(yajlpp_parse_context* ypc,
1,152✔
4610
                                    const value_def* vd,
4611
                                    value_kind_t kind)
4612
{
4613
    if (vd == nullptr) {
1,152✔
4614
        auto retval = logline_value_meta(
4615
            ypc->get_path(), kind, logline_value_meta::external_column{}, this);
6✔
4616

4617
        retval.lvm_hidden = this->jlf_hide_extra;
6✔
4618
        return retval;
6✔
4619
    }
6✔
4620

4621
    auto lvm = vd->vd_meta;
1,146✔
4622

4623
    lvm.lvm_kind = kind;
1,146✔
4624
    return lvm;
1,146✔
4625
}
1,146✔
4626

4627
void
4628
external_log_format::json_append(const json_format_element& jfe,
5,863✔
4629
                                 const value_def* vd,
4630
                                 const string_fragment& sf)
4631
{
4632
    if (jfe.jfe_align == json_format_element::align_t::RIGHT) {
5,863✔
4633
        auto sf_width = sf.column_width();
401✔
4634
        if (sf_width < jfe.jfe_min_width) {
401✔
UNCOV
4635
            this->json_append_to_cache(jfe.jfe_min_width - sf_width);
×
4636
        } else if (jfe.jfe_auto_width && vd != nullptr
32✔
4637
                   && sf_width
433✔
4638
                       < this->lf_value_stats[vd->vd_meta.lvm_values_index
4639
                                                  .value()]
32✔
4640
                             .lvs_width)
32✔
4641
        {
4642
            this->json_append_to_cache(
12✔
4643
                this->lf_value_stats[vd->vd_meta.lvm_values_index.value()]
12✔
4644
                    .lvs_width
12✔
4645
                - sf_width);
12✔
4646
        }
4647
    }
4648
    this->json_append_to_cache(sf.data(), sf.length());
5,863✔
4649
    if ((jfe.jfe_align == json_format_element::align_t::LEFT
5,863✔
4650
         || jfe.jfe_align == json_format_element::align_t::NONE)
5,845✔
4651
        && (jfe.jfe_min_width > 0 || jfe.jfe_auto_width))
5,462✔
4652
    {
4653
        auto sf_width = sf.column_width();
1,065✔
4654
        if (sf_width < jfe.jfe_min_width) {
1,065✔
4655
            this->json_append_to_cache(jfe.jfe_min_width - sf_width);
274✔
4656
        } else if (jfe.jfe_auto_width && vd != nullptr
704✔
4657
                   && sf_width
1,495✔
4658
                       < this->lf_value_stats[vd->vd_meta.lvm_values_index
4659
                                                  .value()]
644✔
4660
                             .lvs_width)
644✔
4661
        {
4662
            this->json_append_to_cache(
377✔
4663
                this->lf_value_stats[vd->vd_meta.lvm_values_index.value()]
377✔
4664
                    .lvs_width
377✔
4665
                - sf_width);
377✔
4666
        }
4667
    }
4668
}
5,863✔
4669

4670
intern_string_t
4671
external_log_format::get_pattern_name(uint64_t line_number) const
42✔
4672
{
4673
    if (this->elf_type != elf_type_t::ELF_TYPE_TEXT) {
42✔
4674
        static auto structured = intern_string::lookup("structured");
4675

UNCOV
4676
        return structured;
×
4677
    }
4678
    int pat_index = this->pattern_index_for_line(line_number);
42✔
4679
    return this->elf_pattern_order[pat_index]->p_name;
42✔
4680
}
4681

4682
int
4683
log_format::pattern_index_for_line(uint64_t line_number) const
6,508✔
4684
{
4685
    if (this->lf_pattern_locks.empty()) {
6,508✔
UNCOV
4686
        return -1;
×
4687
    }
4688

4689
    auto iter = lower_bound(this->lf_pattern_locks.cbegin(),
6,508✔
4690
                            this->lf_pattern_locks.cend(),
4691
                            line_number,
4692
                            [](const pattern_for_lines& pfl, uint32_t line) {
6,558✔
4693
                                return pfl.pfl_line < line;
6,558✔
4694
                            });
4695

4696
    if (iter == this->lf_pattern_locks.end() || iter->pfl_line != line_number) {
6,508✔
4697
        --iter;
5,944✔
4698
    }
4699

4700
    return iter->pfl_pat_index;
6,508✔
4701
}
4702

4703
std::string
UNCOV
4704
log_format::get_pattern_path(uint64_t line_number) const
×
4705
{
UNCOV
4706
    int pat_index = this->pattern_index_for_line(line_number);
×
UNCOV
4707
    return fmt::format(FMT_STRING("builtin ({})"), pat_index);
×
4708
}
4709

4710
intern_string_t
4711
log_format::get_pattern_name(uint64_t line_number) const
6✔
4712
{
4713
    char pat_str[128];
4714

4715
    int pat_index = this->pattern_index_for_line(line_number);
6✔
UNCOV
4716
    auto to_n_res = fmt::format_to_n(
×
4717
        pat_str, sizeof(pat_str) - 1, FMT_STRING("builtin ({})"), pat_index);
18✔
4718
    pat_str[to_n_res.size] = '\0';
6✔
4719
    return intern_string::lookup(pat_str);
12✔
4720
}
4721

4722
std::shared_ptr<log_format>
4723
log_format::find_root_format(const char* name)
1,168✔
4724
{
4725
    auto& fmts = get_root_formats();
1,168✔
4726
    for (auto& lf : fmts) {
44,114✔
4727
        if (lf->get_name() == name) {
44,114✔
4728
            return lf;
1,168✔
4729
        }
4730
    }
UNCOV
4731
    return nullptr;
×
4732
}
4733

4734
exttm
4735
log_format::tm_for_display(logfile::iterator ll, string_fragment sf)
333✔
4736
{
4737
    auto adjusted_time = ll->get_timeval();
333✔
4738
    exttm retval;
333✔
4739

4740
    retval.et_nsec = std::chrono::duration_cast<std::chrono::nanoseconds>(
333✔
UNCOV
4741
                         std::chrono::microseconds{adjusted_time.tv_usec})
×
4742
                         .count();
333✔
4743
    if (this->lf_timestamp_flags & ETF_NANOS_SET) {
333✔
4744
        timeval actual_tv;
UNCOV
4745
        exttm tm;
×
UNCOV
4746
        if (this->lf_date_time.scan(sf.data(),
×
UNCOV
4747
                                    sf.length(),
×
4748
                                    this->get_timestamp_formats(),
4749
                                    &tm,
4750
                                    actual_tv,
4751
                                    false))
4752
        {
UNCOV
4753
            adjusted_time.tv_usec = actual_tv.tv_usec;
×
UNCOV
4754
            retval.et_nsec = tm.et_nsec;
×
4755
        }
4756
    }
4757
    gmtime_r(&adjusted_time.tv_sec, &retval.et_tm);
333✔
4758
    retval.et_flags = this->lf_timestamp_flags;
333✔
4759
    if (this->lf_timestamp_flags & ETF_ZONE_SET
333✔
4760
        && this->lf_date_time.dts_zoned_to_local)
243✔
4761
    {
4762
        retval.et_flags &= ~ETF_Z_IS_UTC;
243✔
4763
    }
4764
    retval.et_gmtoff = this->lf_date_time.dts_local_offset_cache;
333✔
4765

4766
    return retval;
666✔
4767
}
4768

4769
log_format::pattern_for_lines::pattern_for_lines(uint32_t pfl_line,
1,984✔
4770
                                                 uint32_t pfl_pat_index)
1,984✔
4771
    : pfl_line(pfl_line), pfl_pat_index(pfl_pat_index)
1,984✔
4772
{
4773
}
1,984✔
4774

4775
void
4776
logline_value_stats::merge(const logline_value_stats& other)
33✔
4777
{
4778
    if (other.lvs_count == 0) {
33✔
UNCOV
4779
        return;
×
4780
    }
4781

4782
    require(other.lvs_min_value <= other.lvs_max_value);
33✔
4783

4784
    if (other.lvs_width > this->lvs_width) {
33✔
UNCOV
4785
        this->lvs_width = other.lvs_width;
×
4786
    }
4787
    if (other.lvs_min_value < this->lvs_min_value) {
33✔
4788
        this->lvs_min_value = other.lvs_min_value;
33✔
4789
    }
4790
    if (other.lvs_max_value > this->lvs_max_value) {
33✔
4791
        this->lvs_max_value = other.lvs_max_value;
33✔
4792
    }
4793
    this->lvs_count += other.lvs_count;
33✔
4794
    this->lvs_total += other.lvs_total;
33✔
4795

4796
    ensure(this->lvs_count >= 0);
33✔
4797
    ensure(this->lvs_min_value <= this->lvs_max_value);
33✔
4798
}
4799

4800
void
4801
logline_value_stats::add_value(double value)
23,448✔
4802
{
4803
    if (value < this->lvs_min_value) {
23,448✔
4804
        this->lvs_min_value = value;
690✔
4805
    }
4806
    if (value > this->lvs_max_value) {
23,448✔
4807
        this->lvs_max_value = value;
1,341✔
4808
    }
4809
    this->lvs_count += 1;
23,448✔
4810
    this->lvs_total += value;
23,448✔
4811
}
23,448✔
4812

4813
std::vector<logline_value_meta>
4814
external_log_format::get_value_metadata() const
6,948✔
4815
{
4816
    std::vector<logline_value_meta> retval;
6,948✔
4817

4818
    for (const auto& vd : this->elf_value_def_order) {
71,217✔
4819
        retval.emplace_back(vd->vd_meta);
64,269✔
4820
    }
4821

4822
    return retval;
6,948✔
UNCOV
4823
}
×
4824

4825
const logline_value_stats*
UNCOV
4826
external_log_format::stats_for_value(const intern_string_t& name) const
×
4827
{
UNCOV
4828
    const auto iter = this->elf_value_defs.find(name);
×
UNCOV
4829
    if (iter != this->elf_value_defs.end()
×
UNCOV
4830
        && iter->second->vd_meta.lvm_values_index)
×
4831
    {
UNCOV
4832
        return &this->lf_value_stats[iter->second->vd_meta.lvm_values_index
×
UNCOV
4833
                                         .value()];
×
4834
    }
4835

UNCOV
4836
    return nullptr;
×
4837
}
4838

4839
std::string
4840
external_log_format::get_pattern_regex(uint64_t line_number) const
3✔
4841
{
4842
    if (this->elf_type != elf_type_t::ELF_TYPE_TEXT) {
3✔
4843
        return "";
2✔
4844
    }
4845
    int pat_index = this->pattern_index_for_line(line_number);
2✔
4846
    return this->elf_pattern_order[pat_index]->p_pcre.pp_value->get_pattern();
2✔
4847
}
4848

4849
bool
4850
external_log_format::hide_field(const intern_string_t field_name, bool val)
11✔
4851
{
4852
    const auto vd_iter = this->elf_value_defs.find(field_name);
11✔
4853
    if (vd_iter == this->elf_value_defs.end()) {
11✔
4854
        return false;
1✔
4855
    }
4856

4857
    vd_iter->second->vd_meta.lvm_user_hidden = val;
10✔
4858
    if (this->elf_type == elf_type_t::ELF_TYPE_JSON) {
10✔
4859
        bool found = false;
1✔
4860

4861
        for (const auto& jfe : this->jlf_line_format) {
18✔
4862
            if (jfe.jfe_value.pp_value == field_name) {
17✔
UNCOV
4863
                found = true;
×
4864
            }
4865
        }
4866
        if (!found) {
1✔
4867
            log_info("format field %s.%s changed, rebuilding",
1✔
4868
                     this->elf_name.get(),
4869
                     field_name.get());
4870
            this->elf_value_defs_state->vds_generation += 1;
1✔
4871
        }
4872
    }
4873
    return true;
10✔
4874
}
4875

4876
bool
4877
external_log_format::format_changed()
2,421✔
4878
{
4879
    if (this->elf_specialized_value_defs_state.vds_generation
4,842✔
4880
        != this->elf_value_defs_state->vds_generation)
2,421✔
4881
    {
4882
        this->elf_specialized_value_defs_state = *this->elf_value_defs_state;
1✔
4883
        this->jlf_cached_offset = -1;
1✔
4884
        return true;
1✔
4885
    }
4886

4887
    return false;
2,420✔
4888
}
4889

4890
bool
4891
format_tag_def::path_restriction::matches(const char* fn) const
4✔
4892
{
4893
    return fnmatch(this->p_glob.c_str(), fn, 0) == 0;
4✔
4894
}
4895

4896
bool
4897
format_partition_def::path_restriction::matches(const char* fn) const
4✔
4898
{
4899
    return fnmatch(this->p_glob.c_str(), fn, 0) == 0;
4✔
4900
}
4901

4902
/* XXX */
4903
#include "log_format_impls.cc"
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc