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

tstack / lnav / 21802468722-2778

08 Feb 2026 05:40PM UTC coverage: 68.978% (+0.01%) from 68.966%
21802468722-2778

push

github

tstack
[formats] allow highlights to be limited to a field

Related to #1620

218 of 325 new or added lines in 11 files covered. (67.08%)

2 existing lines in 2 files now uncovered.

51991 of 75373 relevant lines covered (68.98%)

440117.55 hits per line

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

84.85
/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 <chrono>
32
#include <memory>
33
#include <vector>
34

35
#include <fnmatch.h>
36
#include <stdio.h>
37
#include <string.h>
38

39
#include "base/fs_util.hh"
40
#include "base/humanize.hh"
41
#include "base/intern_string.hh"
42
#include "base/is_utf8.hh"
43
#include "base/itertools.enumerate.hh"
44
#include "base/itertools.hh"
45
#include "base/map_util.hh"
46
#include "base/opt_util.hh"
47
#include "base/snippet_highlighters.hh"
48
#include "base/string_attr_type.hh"
49
#include "base/string_util.hh"
50
#include "bookmarks.hh"
51
#include "command_executor.hh"
52
#include "config.h"
53
#include "fmt/format.h"
54
#include "lnav_util.hh"
55
#include "log_format_ext.hh"
56
#include "log_search_table.hh"
57
#include "log_vtab_impl.hh"
58
#include "logfile_sub_source.hh"
59
#include "ptimec.hh"
60
#include "readline_highlighters.hh"
61
#include "scn/scan.h"
62
#include "sql_util.hh"
63
#include "sqlite-extension-func.hh"
64
#include "sqlitepp.hh"
65
#include "yajlpp/yajlpp.hh"
66
#include "yajlpp/yajlpp_def.hh"
67

68
using namespace lnav::roles::literals;
69
using namespace std::chrono_literals;
70
using std::string_literals::operator""s;
71

72
static auto intern_lifetime = intern_string::get_table_lifetime();
73

74
constexpr string_attr_type<void> L_PREFIX("prefix");
75
constexpr string_attr_type<void> L_TIMESTAMP("timestamp");
76
constexpr string_attr_type<void> L_LEVEL("level");
77
constexpr string_attr_type<std::shared_ptr<logfile>> L_FILE("file");
78
constexpr string_attr_type<bookmark_metadata*> L_PARTITION("partition");
79
constexpr string_attr_type<void> L_OPID("opid");
80
constexpr string_attr_type<bookmark_metadata*> L_META("meta");
81

82
std::vector<std::shared_ptr<external_log_format>>
83
    external_log_format::GRAPH_ORDERED_FORMATS;
84

85
const intern_string_t log_format::LOG_TIME_STR
86
    = intern_string::lookup("log_time");
87
const intern_string_t log_format::LOG_LEVEL_STR
88
    = intern_string::lookup("log_level");
89
const intern_string_t log_format::LOG_OPID_STR
90
    = intern_string::lookup("log_opid");
91
const intern_string_t log_format::LOG_THREAD_ID_STR
92
    = intern_string::lookup("log_thread_id");
93

94
static constexpr uint32_t DATE_TIME_SET_FLAGS = ETF_YEAR_SET | ETF_MONTH_SET
95
    | ETF_DAY_SET | ETF_HOUR_SET | ETF_MINUTE_SET | ETF_SECOND_SET;
96

97
log_level_stats&
98
log_level_stats::operator|=(const log_level_stats& rhs)
855✔
99
{
100
    this->lls_error_count += rhs.lls_error_count;
855✔
101
    this->lls_warning_count += rhs.lls_warning_count;
855✔
102
    this->lls_total_count += rhs.lls_total_count;
855✔
103

104
    return *this;
855✔
105
}
106

107
log_op_description&
108
log_op_description::operator|=(const log_op_description& rhs)
371✔
109
{
110
    if (!this->lod_index && rhs.lod_index) {
371✔
111
        this->lod_index = rhs.lod_index;
×
112
    }
113
    if (this->lod_elements.size() < rhs.lod_elements.size()) {
371✔
114
        this->lod_elements = rhs.lod_elements;
×
115
    }
116

117
    return *this;
371✔
118
}
119

120
void
121
opid_time_range::clear()
×
122
{
123
    this->otr_range.invalidate();
×
124
    this->otr_sub_ops.clear();
×
125
    this->otr_level_stats = {};
×
126
}
127

128
opid_time_range&
129
opid_time_range::operator|=(const opid_time_range& rhs)
371✔
130
{
131
    this->otr_range |= rhs.otr_range;
371✔
132
    this->otr_description |= rhs.otr_description;
371✔
133
    this->otr_level_stats |= rhs.otr_level_stats;
371✔
134
    for (const auto& rhs_sub : rhs.otr_sub_ops) {
376✔
135
        bool found = false;
5✔
136

137
        for (auto& sub : this->otr_sub_ops) {
10✔
138
            if (sub.ostr_subid == rhs_sub.ostr_subid) {
5✔
139
                sub.ostr_range |= rhs_sub.ostr_range;
5✔
140
                found = true;
5✔
141
            }
142
        }
143
        if (!found) {
5✔
144
            this->otr_sub_ops.emplace_back(rhs_sub);
×
145
        }
146
    }
147
    std::stable_sort(this->otr_sub_ops.begin(), this->otr_sub_ops.end());
371✔
148

149
    return *this;
371✔
150
}
151

152
void
153
thread_id_time_range::clear()
×
154
{
155
    this->titr_range.invalidate();
×
156
    this->titr_level_stats = {};
×
157
}
158

159
thread_id_time_range&
160
thread_id_time_range::operator|=(const thread_id_time_range& rhs)
484✔
161
{
162
    this->titr_range |= rhs.titr_range;
484✔
163
    this->titr_level_stats |= rhs.titr_level_stats;
484✔
164

165
    return *this;
484✔
166
}
167

168
void
169
log_level_stats::update_msg_count(log_level_t lvl, int32_t amount)
38,852✔
170
{
171
    switch (lvl) {
38,852✔
172
        case LEVEL_FATAL:
3,110✔
173
        case LEVEL_CRITICAL:
174
        case LEVEL_ERROR:
175
            this->lls_error_count += amount;
3,110✔
176
            break;
3,110✔
177
        case LEVEL_WARNING:
196✔
178
            this->lls_warning_count += amount;
196✔
179
            break;
196✔
180
        default:
35,546✔
181
            break;
35,546✔
182
    }
183
    this->lls_total_count += amount;
38,852✔
184
}
38,852✔
185

186
void
187
opid_time_range::close_sub_ops(const string_fragment& subid)
×
188
{
189
    for (auto& other_sub : this->otr_sub_ops) {
×
190
        if (other_sub.ostr_subid == subid) {
×
191
            other_sub.ostr_open = false;
×
192
        }
193
    }
194
}
195

196
log_thread_id_map::iterator
197
log_thread_id_state::insert_tid(ArenaAlloc::Alloc<char>& alloc,
12,312✔
198
                                const string_fragment& tid,
199
                                const std::chrono::microseconds& us)
200
{
201
    auto retval = this->ltis_tid_ranges.find(tid);
12,312✔
202
    if (retval == this->ltis_tid_ranges.end()) {
12,312✔
203
        auto tid_copy = tid.to_owned(alloc);
7,126✔
204
        auto titr = thread_id_time_range{time_range{us, us}};
7,126✔
205
        auto emplace_res = this->ltis_tid_ranges.emplace(tid_copy, titr);
7,126✔
206
        retval = emplace_res.first;
7,126✔
207
    } else {
208
        retval->second.titr_range.extend_to(us);
5,186✔
209
    }
210

211
    return retval;
12,312✔
212
}
213

214
log_opid_map::iterator
215
log_opid_state::insert_op(ArenaAlloc::Alloc<char>& alloc,
12,995✔
216
                          const string_fragment& opid,
217
                          const std::chrono::microseconds& us,
218
                          timestamp_point_of_reference_t poref,
219
                          std::chrono::microseconds duration)
220
{
221
    auto retval = this->los_opid_ranges.find(opid);
12,995✔
222
    if (retval == this->los_opid_ranges.end()) {
12,995✔
223
        auto opid_copy = opid.to_owned(alloc);
6,789✔
224
        auto otr = opid_time_range{time_range{us, us}};
6,789✔
225
        auto emplace_res = this->los_opid_ranges.emplace(opid_copy, otr);
6,789✔
226
        retval = emplace_res.first;
6,789✔
227
    } else {
6,789✔
228
        retval->second.otr_range.extend_to(us);
6,206✔
229
    }
230
    if (duration > 0us) {
12,995✔
231
        auto other_us = us;
104✔
232
        switch (poref) {
104✔
233
            case timestamp_point_of_reference_t::send:
18✔
234
                other_us -= duration;
18✔
235
                break;
18✔
236
            case timestamp_point_of_reference_t::start:
86✔
237
                other_us += duration;
86✔
238
                break;
86✔
239
        }
240
        retval->second.otr_range.extend_to(other_us);
104✔
241
    }
242

243
    return retval;
12,995✔
244
}
245

246
opid_sub_time_range*
247
log_opid_state::sub_op_in_use(ArenaAlloc::Alloc<char>& alloc,
62✔
248
                              log_opid_map::iterator& op_iter,
249
                              const string_fragment& subid,
250
                              const std::chrono::microseconds& us,
251
                              log_level_t level)
252
{
253
    const auto& opid = op_iter->first;
62✔
254
    auto sub_iter = this->los_sub_in_use.find(subid);
62✔
255
    if (sub_iter == this->los_sub_in_use.end()) {
62✔
256
        auto emp_res
257
            = this->los_sub_in_use.emplace(subid.to_owned(alloc), opid);
41✔
258

259
        sub_iter = emp_res.first;
41✔
260
    }
261

262
    auto retval = sub_iter->first;
62✔
263
    if (sub_iter->second != opid) {
62✔
264
        auto other_otr
265
            = lnav::map::find(this->los_opid_ranges, sub_iter->second);
×
266
        if (other_otr) {
×
267
            other_otr->get().close_sub_ops(retval);
×
268
        }
269
    }
270
    sub_iter->second = opid;
62✔
271

272
    auto& otr = op_iter->second;
62✔
273
    auto sub_op_iter = otr.otr_sub_ops.rbegin();
62✔
274
    for (; sub_op_iter != otr.otr_sub_ops.rend(); ++sub_op_iter) {
62✔
275
        if (sub_op_iter->ostr_open && sub_op_iter->ostr_subid == retval) {
21✔
276
            break;
21✔
277
        }
278
    }
279
    if (sub_op_iter == otr.otr_sub_ops.rend()) {
62✔
280
        otr.otr_sub_ops.emplace_back(opid_sub_time_range{
41✔
281
            retval,
282
            time_range{us, us},
283
        });
284
        otr.otr_sub_ops.back().ostr_level_stats.update_msg_count(level);
41✔
285

286
        return &otr.otr_sub_ops.back();
41✔
287
    } else {
288
        sub_op_iter->ostr_range.extend_to(us);
21✔
289
        sub_op_iter->ostr_level_stats.update_msg_count(level);
21✔
290
        return &(*sub_op_iter);
21✔
291
    }
292
}
293

294
std::optional<std::string>
295
log_format::opid_descriptor::matches(const string_fragment& sf) const
2,148✔
296
{
297
    if (this->od_extractor.pp_value) {
2,148✔
298
        thread_local auto desc_md = lnav::pcre2pp::match_data::unitialized();
334✔
299

300
        auto desc_match_res = this->od_extractor.pp_value->capture_from(sf)
334✔
301
                                  .into(desc_md)
334✔
302
                                  .matches(PCRE2_NO_UTF_CHECK | PCRE2_ANCHORED)
668✔
303
                                  .ignore_error();
334✔
304
        if (desc_match_res) {
334✔
305
            return desc_md.to_string();
66✔
306
        }
307

308
        return std::nullopt;
268✔
309
    }
310
    return sf.to_string();
1,814✔
311
}
312

313
std::string
314
log_format::opid_descriptors::to_string(
1,025✔
315
    const lnav::map::small<size_t, std::string>& lod) const
316
{
317
    std::string retval;
1,025✔
318

319
    for (size_t lpc = 0; lpc < this->od_descriptors->size(); lpc++) {
2,063✔
320
        retval.append(this->od_descriptors->at(lpc).od_prefix);
1,038✔
321
        auto val = lod.value_for(lpc);
1,038✔
322
        if (val) {
1,038✔
323
            retval.append(*val.value());
1,036✔
324
        }
325
        retval.append(this->od_descriptors->at(lpc).od_suffix);
1,038✔
326
    }
327

328
    return retval;
1,025✔
329
}
×
330

331
bool
332
logline_value_meta::is_numeric() const
×
333
{
334
    if (this->lvm_identifier || this->lvm_foreign_key) {
×
335
        return false;
×
336
    }
337
    switch (this->lvm_kind) {
×
338
        case value_kind_t::VALUE_FLOAT:
×
339
        case value_kind_t::VALUE_INTEGER:
340
            return true;
×
341
        default:
×
342
            return false;
×
343
    }
344
}
345

346
chart_type_t
347
logline_value_meta::to_chart_type() const
54✔
348
{
349
    auto retval = chart_type_t::hist;
54✔
350
    switch (this->lvm_kind) {
54✔
351
        case value_kind_t::VALUE_NULL:
9✔
352
            retval = chart_type_t::none;
9✔
353
            break;
9✔
354
        case value_kind_t::VALUE_INTEGER:
10✔
355
            if (!this->lvm_identifier && !this->lvm_foreign_key) {
10✔
356
                retval = chart_type_t::spectro;
5✔
357
            }
358
            break;
10✔
359
        case value_kind_t::VALUE_FLOAT:
×
360
            retval = chart_type_t::spectro;
×
361
            break;
×
362
        case value_kind_t::VALUE_XML:
1✔
363
        case value_kind_t::VALUE_JSON:
364
        case value_kind_t::VALUE_BOOLEAN:
365
        case value_kind_t::VALUE_TIMESTAMP:
366
            retval = chart_type_t::none;
1✔
367
            break;
1✔
368
        default:
34✔
369
            break;
34✔
370
    }
371

372
    return retval;
54✔
373
}
374

375
struct line_range
376
logline_value::origin_in_full_msg(const char* msg, ssize_t len) const
×
377
{
378
    if (this->lv_sub_offset == 0) {
×
379
        return this->lv_origin;
×
380
    }
381

382
    if (len == -1) {
×
383
        len = strlen(msg);
×
384
    }
385

386
    struct line_range retval = this->lv_origin;
×
387
    const char *last = msg, *msg_end = msg + len;
×
388

389
    for (int lpc = 0; lpc < this->lv_sub_offset; lpc++) {
×
390
        const auto* next = (const char*) memchr(last, '\n', msg_end - last);
×
391
        require(next != nullptr);
×
392

393
        next += 1;
×
394
        int amount = (next - last);
×
395

396
        retval.lr_start += amount;
×
397
        if (retval.lr_end != -1) {
×
398
            retval.lr_end += amount;
×
399
        }
400

401
        last = next + 1;
×
402
    }
403

404
    if (retval.lr_end == -1) {
×
405
        const auto* eol = (const char*) memchr(last, '\n', msg_end - last);
×
406

407
        if (eol == nullptr) {
×
408
            retval.lr_end = len;
×
409
        } else {
410
            retval.lr_end = eol - msg;
×
411
        }
412
    }
413

414
    return retval;
×
415
}
416

417
logline_value::logline_value(logline_value_meta lvm,
641,180✔
418
                             shared_buffer_ref& sbr,
419
                             struct line_range origin)
641,180✔
420
    : lv_meta(std::move(lvm)), lv_origin(origin)
641,180✔
421
{
422
    if (sbr.get_data() == nullptr) {
641,180✔
423
        this->lv_meta.lvm_kind = value_kind_t::VALUE_NULL;
×
424
    }
425

426
    switch (this->lv_meta.lvm_kind) {
641,180✔
427
        case value_kind_t::VALUE_JSON:
327,314✔
428
        case value_kind_t::VALUE_XML:
429
        case value_kind_t::VALUE_STRUCT:
430
        case value_kind_t::VALUE_TEXT:
431
        case value_kind_t::VALUE_QUOTED:
432
        case value_kind_t::VALUE_W3C_QUOTED:
433
        case value_kind_t::VALUE_TIMESTAMP:
434
            require(origin.lr_end != -1);
327,314✔
435
            this->lv_frag = string_fragment::from_byte_range(
327,314✔
436
                sbr.get_data(), origin.lr_start, origin.lr_end);
327,314✔
437
            break;
327,314✔
438

439
        case value_kind_t::VALUE_NULL:
×
440
            break;
×
441

442
        case value_kind_t::VALUE_INTEGER: {
283,569✔
443
            auto scan_res
444
                = scn::scan_value<int64_t>(sbr.to_string_view(origin));
283,569✔
445
            if (scan_res) {
283,569✔
446
                this->lv_value.i = scan_res->value();
283,567✔
447
            } else {
448
                this->lv_value.i = 0;
2✔
449
            }
450
            break;
283,569✔
451
        }
452

453
        case value_kind_t::VALUE_FLOAT: {
30,297✔
454
            auto scan_res = scn::scan_value<double>(sbr.to_string_view(origin));
30,297✔
455
            if (scan_res) {
30,297✔
456
                this->lv_value.d = scan_res->value();
30,297✔
457
            } else {
458
                this->lv_value.d = 0;
×
459
            }
460
            break;
30,297✔
461
        }
462

463
        case value_kind_t::VALUE_BOOLEAN:
×
464
            if (strncmp(
×
465
                    sbr.get_data_at(origin.lr_start), "true", origin.length())
×
466
                    == 0
467
                || strncmp(
×
468
                       sbr.get_data_at(origin.lr_start), "yes", origin.length())
×
469
                    == 0)
470
            {
471
                this->lv_value.i = 1;
×
472
            } else {
473
                this->lv_value.i = 0;
×
474
            }
475
            break;
×
476

477
        case value_kind_t::VALUE_UNKNOWN:
×
478
        case value_kind_t::VALUE__MAX:
479
            ensure(0);
×
480
            break;
481
    }
482
}
641,180✔
483

484
void
485
logline_value::apply_scaling(const scaling_factor* sf)
43,042✔
486
{
487
    if (sf != nullptr) {
43,042✔
488
        switch (this->lv_meta.lvm_kind) {
10✔
489
            case value_kind_t::VALUE_INTEGER:
×
490
                sf->scale(this->lv_value.i);
×
491
                break;
×
492
            case value_kind_t::VALUE_FLOAT:
10✔
493
                sf->scale(this->lv_value.d);
10✔
494
                break;
10✔
495
            default:
×
496
                break;
×
497
        }
498
    }
499
}
43,042✔
500

501
std::string
502
logline_value::to_string() const
10,052✔
503
{
504
    char buffer[128];
505

506
    switch (this->lv_meta.lvm_kind) {
10,052✔
507
        case value_kind_t::VALUE_NULL:
15✔
508
            return "null";
30✔
509

510
        case value_kind_t::VALUE_JSON:
9,499✔
511
        case value_kind_t::VALUE_XML:
512
        case value_kind_t::VALUE_STRUCT:
513
        case value_kind_t::VALUE_TEXT:
514
        case value_kind_t::VALUE_TIMESTAMP:
515
            if (this->lv_str) {
9,499✔
516
                return this->lv_str.value();
1,480✔
517
            }
518
            if (this->lv_frag.empty()) {
8,019✔
519
                return this->lv_intern_string.to_string();
47✔
520
            }
521
            return this->lv_frag.to_string();
7,972✔
522

523
        case value_kind_t::VALUE_QUOTED:
7✔
524
        case value_kind_t::VALUE_W3C_QUOTED:
525
            if (this->lv_frag.empty()) {
7✔
526
                return "";
×
527
            } else {
528
                switch (this->lv_frag.data()[0]) {
7✔
529
                    case '\'':
7✔
530
                    case '"': {
531
                        auto unquote_func = this->lv_meta.lvm_kind
14✔
532
                                == value_kind_t::VALUE_W3C_QUOTED
533
                            ? unquote_w3c
7✔
534
                            : unquote;
535
                        stack_buf allocator;
7✔
536
                        auto* unquoted_str
537
                            = allocator.allocate(this->lv_frag.length());
7✔
538
                        size_t unquoted_len;
539

540
                        unquoted_len = unquote_func(unquoted_str,
7✔
541
                                                    this->lv_frag.data(),
542
                                                    this->lv_frag.length());
7✔
543
                        return {unquoted_str, unquoted_len};
14✔
544
                    }
7✔
545
                    default:
×
546
                        return this->lv_frag.to_string();
×
547
                }
548
            }
549
            break;
550

551
        case value_kind_t::VALUE_INTEGER:
501✔
552
            snprintf(buffer, sizeof(buffer), "%" PRId64, this->lv_value.i);
501✔
553
            break;
501✔
554

555
        case value_kind_t::VALUE_FLOAT:
24✔
556
            snprintf(buffer, sizeof(buffer), "%lf", this->lv_value.d);
24✔
557
            break;
24✔
558

559
        case value_kind_t::VALUE_BOOLEAN:
6✔
560
            if (this->lv_value.i) {
6✔
561
                return "true";
×
562
            } else {
563
                return "false";
12✔
564
            }
565
            break;
566
        case value_kind_t::VALUE_UNKNOWN:
×
567
        case value_kind_t::VALUE__MAX:
568
            ensure(0);
×
569
            break;
570
    }
571

572
    return {buffer};
1,050✔
573
}
574

575
string_fragment
576
logline_value::to_string_fragment(ArenaAlloc::Alloc<char>& alloc) const
1,095✔
577
{
578
    char buffer[128];
579

580
    switch (this->lv_meta.lvm_kind) {
1,095✔
581
        case value_kind_t::VALUE_NULL:
×
582
            return "null"_frag;
×
583

584
        case value_kind_t::VALUE_JSON:
1,095✔
585
        case value_kind_t::VALUE_XML:
586
        case value_kind_t::VALUE_STRUCT:
587
        case value_kind_t::VALUE_TEXT:
588
        case value_kind_t::VALUE_TIMESTAMP:
589
            if (this->lv_str) {
1,095✔
590
                return string_fragment::from_str(this->lv_str.value())
6✔
591
                    .to_owned(alloc);
6✔
592
            }
593
            if (this->lv_frag.empty()) {
1,089✔
594
                return this->lv_intern_string.to_string_fragment().to_owned(
×
595
                    alloc);
×
596
            }
597
            return this->lv_frag.to_owned(alloc);
1,089✔
598

599
        case value_kind_t::VALUE_QUOTED:
×
600
        case value_kind_t::VALUE_W3C_QUOTED:
601
            if (this->lv_frag.empty()) {
×
602
                return string_fragment{};
×
603
            } else {
604
                switch (this->lv_frag.data()[0]) {
×
605
                    case '\'':
×
606
                    case '"': {
607
                        auto unquote_func = this->lv_meta.lvm_kind
×
608
                                == value_kind_t::VALUE_W3C_QUOTED
609
                            ? unquote_w3c
×
610
                            : unquote;
611
                        stack_buf allocator;
×
612
                        auto* unquoted_str
613
                            = allocator.allocate(this->lv_frag.length());
×
614
                        size_t unquoted_len;
615

616
                        unquoted_len = unquote_func(unquoted_str,
×
617
                                                    this->lv_frag.data(),
618
                                                    this->lv_frag.length());
×
619
                        return string_fragment::from_bytes(unquoted_str,
×
620
                                                           unquoted_len)
621
                            .to_owned(alloc);
×
622
                    }
623
                    default:
×
624
                        return this->lv_frag.to_owned(alloc);
×
625
                }
626
            }
627
            break;
628

629
        case value_kind_t::VALUE_INTEGER:
×
630
            snprintf(buffer, sizeof(buffer), "%" PRId64, this->lv_value.i);
×
631
            break;
×
632

633
        case value_kind_t::VALUE_FLOAT:
×
634
            snprintf(buffer, sizeof(buffer), "%lf", this->lv_value.d);
×
635
            break;
×
636

637
        case value_kind_t::VALUE_BOOLEAN:
×
638
            if (this->lv_value.i) {
×
639
                return "true"_frag;
×
640
            }
641
            return "false"_frag;
×
642
            break;
643
        case value_kind_t::VALUE_UNKNOWN:
×
644
        case value_kind_t::VALUE__MAX:
645
            ensure(0);
×
646
            break;
647
    }
648

649
    return string_fragment::from_c_str(buffer).to_owned(alloc);
×
650
}
651

652
const char*
653
logline_value::text_value() const
68,528✔
654
{
655
    if (this->lv_str) {
68,528✔
656
        return this->lv_str->c_str();
159✔
657
    }
658
    if (this->lv_frag.empty()) {
68,369✔
659
        if (this->lv_intern_string.empty()) {
15✔
660
            return "";
15✔
661
        }
662
        return this->lv_intern_string.get();
×
663
    }
664
    return this->lv_frag.data();
68,354✔
665
}
666

667
size_t
668
logline_value::text_length() const
68,561✔
669
{
670
    if (this->lv_str) {
68,561✔
671
        return this->lv_str->size();
159✔
672
    }
673
    if (this->lv_frag.empty()) {
68,402✔
674
        return this->lv_intern_string.size();
15✔
675
    }
676
    return this->lv_frag.length();
68,387✔
677
}
678

679
string_fragment
680
logline_value::text_value_fragment() const
×
681
{
682
    return string_fragment::from_bytes(this->text_value(), this->text_length());
×
683
}
684

685
void
686
logline_value_vector::clear()
42,339✔
687
{
688
    this->lvv_values.clear();
42,339✔
689
    this->lvv_sbr.disown();
42,339✔
690
    this->lvv_opid_value = std::nullopt;
42,339✔
691
    this->lvv_opid_provenance = opid_provenance::none;
42,339✔
692
    this->lvv_thread_id_value = std::nullopt;
42,339✔
693
}
42,339✔
694

695
logline_value_vector::logline_value_vector(const logline_value_vector& other)
810✔
696
    : lvv_sbr(other.lvv_sbr.clone()), lvv_values(other.lvv_values),
810✔
697
      lvv_opid_value(other.lvv_opid_value),
810✔
698
      lvv_opid_provenance(other.lvv_opid_provenance),
810✔
699
      lvv_thread_id_value(other.lvv_thread_id_value)
810✔
700
{
701
}
810✔
702

703
logline_value_vector&
704
logline_value_vector::operator=(const logline_value_vector& other)
414✔
705
{
706
    this->lvv_sbr = other.lvv_sbr.clone();
414✔
707
    this->lvv_values = other.lvv_values;
414✔
708
    this->lvv_opid_value = other.lvv_opid_value;
414✔
709
    this->lvv_opid_provenance = other.lvv_opid_provenance;
414✔
710
    this->lvv_thread_id_value = other.lvv_thread_id_value;
414✔
711

712
    return *this;
414✔
713
}
714

715
std::vector<std::shared_ptr<log_format>> log_format::lf_root_formats;
716

717
std::vector<std::shared_ptr<log_format>>&
718
log_format::get_root_formats()
17,347✔
719
{
720
    return lf_root_formats;
17,347✔
721
}
722

723
void
724
external_log_format::update_op_description(
4,960✔
725
    const std::vector<opid_descriptors*>& desc_defs_vec,
726
    log_op_description& lod,
727
    const pattern* fpat,
728
    const lnav::pcre2pp::match_data& md)
729
{
730
    std::optional<std::string> desc_elem_str;
4,960✔
731
    if (!lod.lod_index) {
4,960✔
732
        for (const auto& desc_defs : desc_defs_vec) {
3,001✔
733
            if (lod.lod_index) {
1,255✔
734
                break;
26✔
735
            }
736
            for (const auto& desc_def : *desc_defs->od_descriptors) {
1,537✔
737
                auto desc_field_index_iter = fpat->p_value_name_to_index.find(
1,327✔
738
                    desc_def.od_field.pp_value);
1,327✔
739

740
                if (desc_field_index_iter == fpat->p_value_name_to_index.end())
1,327✔
741
                {
742
                    continue;
43✔
743
                }
744

745
                auto desc_cap_opt = md[desc_field_index_iter->second];
1,323✔
746
                if (!desc_cap_opt) {
1,323✔
747
                    continue;
39✔
748
                }
749

750
                desc_elem_str = desc_def.matches(desc_cap_opt.value());
1,284✔
751
                if (desc_elem_str) {
1,284✔
752
                    lod.lod_index = desc_defs->od_index;
1,019✔
753
                    break;
1,019✔
754
                }
755
            }
756
        }
757
    }
758
    if (lod.lod_index) {
4,960✔
759
        const auto& desc_def_v
760
            = *desc_defs_vec[lod.lod_index.value()]->od_descriptors;
4,207✔
761
        auto& desc_v = lod.lod_elements;
4,207✔
762

763
        if (desc_def_v.size() == desc_v.size()
4,207✔
764
            || (this->elf_opid_field.empty() && !desc_v.empty()))
4,207✔
765
        {
766
            return;
3,188✔
767
        }
768
        for (size_t desc_def_index = 0; desc_def_index < desc_def_v.size();
2,908✔
769
             desc_def_index++)
770
        {
771
            const auto& desc_def = desc_def_v[desc_def_index];
1,889✔
772
            auto found_desc = desc_v.value_for(desc_def_index);
1,889✔
773
            auto desc_field_index_iter
774
                = fpat->p_value_name_to_index.find(desc_def.od_field.pp_value);
1,889✔
775

776
            if (desc_field_index_iter == fpat->p_value_name_to_index.end()) {
1,889✔
777
                continue;
36✔
778
            }
779
            auto desc_cap_opt = md[desc_field_index_iter->second];
1,889✔
780
            if (!desc_cap_opt) {
1,889✔
781
                continue;
36✔
782
            }
783

784
            if (!desc_elem_str) {
1,853✔
785
                desc_elem_str = desc_def.matches(desc_cap_opt.value());
834✔
786
            }
787
            if (desc_elem_str) {
1,853✔
788
                if (!found_desc) {
1,850✔
789
                    desc_v.insert(desc_def_index, desc_elem_str.value());
1,850✔
790
                } else if (!desc_elem_str->empty()) {
×
791
                    found_desc.value()->append(desc_def.od_joiner);
×
792
                    found_desc.value()->append(desc_elem_str.value());
×
793
                }
794
            }
795
            desc_elem_str = std::nullopt;
1,853✔
796
        }
797
    }
798
}
4,960✔
799

800
void
801
external_log_format::update_op_description(
3,229✔
802
    const std::vector<opid_descriptors*>& desc_defs_vec,
803
    log_op_description& lod)
804
{
805
    std::optional<std::string> desc_elem_str;
3,229✔
806
    if (!lod.lod_index) {
3,229✔
807
        for (const auto& desc_defs : desc_defs_vec) {
3,238✔
808
            if (lod.lod_index) {
15✔
809
                break;
×
810
            }
811
            for (const auto& desc_def : *desc_defs->od_descriptors) {
15✔
812
                auto desc_cap_iter
813
                    = this->lf_desc_captures.find(desc_def.od_field.pp_value);
15✔
814

815
                if (desc_cap_iter == this->lf_desc_captures.end()) {
15✔
816
                    continue;
×
817
                }
818
                desc_elem_str = desc_def.matches(desc_cap_iter->second);
15✔
819
                if (desc_elem_str) {
15✔
820
                    lod.lod_index = desc_defs->od_index;
15✔
821
                    break;
15✔
822
                }
823
            }
824
        }
825
    }
826
    if (lod.lod_index) {
3,229✔
827
        const auto& desc_def_v
828
            = *desc_defs_vec[lod.lod_index.value()]->od_descriptors;
21✔
829
        auto& desc_v = lod.lod_elements;
21✔
830

831
        if (desc_def_v.size() == desc_v.size()
21✔
832
            || (this->elf_opid_field.empty() && !desc_v.empty()))
21✔
833
        {
834
            return;
6✔
835
        }
836
        for (size_t desc_def_index = 0; desc_def_index < desc_def_v.size();
45✔
837
             desc_def_index++)
838
        {
839
            const auto& desc_def = desc_def_v[desc_def_index];
30✔
840
            auto found_desc = desc_v.value_for(desc_def_index);
30✔
841
            auto desc_cap_iter
842
                = this->lf_desc_captures.find(desc_def.od_field.pp_value);
30✔
843
            if (desc_cap_iter == this->lf_desc_captures.end()) {
30✔
844
                continue;
×
845
            }
846

847
            if (!desc_elem_str) {
30✔
848
                desc_elem_str = desc_def.matches(desc_cap_iter->second);
15✔
849
            }
850
            if (desc_elem_str) {
30✔
851
                if (!found_desc) {
30✔
852
                    desc_v.insert(desc_def_index, desc_elem_str.value());
30✔
853
                } else if (!desc_elem_str->empty()) {
×
854
                    found_desc.value()->append(desc_def.od_joiner);
×
855
                    found_desc.value()->append(desc_elem_str.value());
×
856
                }
857
            }
858
            desc_elem_str = std::nullopt;
30✔
859
        }
860
    }
861
}
3,229✔
862

863
static bool
864
next_format(
2,272,512✔
865
    const std::vector<std::shared_ptr<external_log_format::pattern>>& patterns,
866
    int& index,
867
    int& locked_index)
868
{
869
    bool retval = true;
2,272,512✔
870

871
    if (locked_index == -1) {
2,272,512✔
872
        index += 1;
2,265,217✔
873
        if (index >= (int) patterns.size()) {
2,265,217✔
874
            retval = false;
686,302✔
875
        }
876
    } else if (index == locked_index) {
7,295✔
877
        retval = false;
1✔
878
    } else {
879
        index = locked_index;
7,294✔
880
    }
881

882
    return retval;
2,272,512✔
883
}
884

885
bool
886
log_format::next_format(const pcre_format* fmt, int& index, int& locked_index)
177,063✔
887
{
888
    bool retval = true;
177,063✔
889

890
    if (locked_index == -1) {
177,063✔
891
        index += 1;
176,881✔
892
        if (fmt[index].name == nullptr) {
176,881✔
893
            retval = false;
9,946✔
894
        }
895
    } else if (index == locked_index) {
182✔
896
        retval = false;
36✔
897
    } else {
898
        index = locked_index;
146✔
899
    }
900

901
    return retval;
177,063✔
902
}
903

904
const char*
905
log_format::log_scanf(scan_batch_context& sbc,
12,373✔
906
                      uint32_t line_number,
907
                      string_fragment line,
908
                      const pcre_format* fmt,
909
                      const char* time_fmt[],
910
                      exttm* tm_out,
911
                      timeval* tv_out,
912

913
                      string_fragment* ts_out,
914
                      std::optional<string_fragment>* level_out)
915
{
916
    int curr_fmt = -1;
12,373✔
917
    const char* retval = nullptr;
12,373✔
918
    bool done = false;
12,373✔
919
    int pat_index = sbc.sbc_pattern_locks.last_pattern_index();
12,373✔
920

921
    while (!done && next_format(fmt, curr_fmt, pat_index)) {
179,454✔
922
        thread_local auto md = lnav::pcre2pp::match_data::unitialized();
167,081✔
923

924
        auto match_res = fmt[curr_fmt]
167,081✔
925
                             .pcre->capture_from(line)
167,081✔
926
                             .into(md)
167,081✔
927
                             .matches(PCRE2_NO_UTF_CHECK)
334,162✔
928
                             .ignore_error();
167,081✔
929
        if (!match_res) {
167,081✔
930
            retval = nullptr;
143,534✔
931
        } else {
932
            auto ts = md[fmt[curr_fmt].pf_timestamp_index];
23,547✔
933

934
            retval = this->lf_date_time.scan(
23,547✔
935
                ts->data(), ts->length(), nullptr, tm_out, *tv_out);
23,547✔
936

937
            if (retval == nullptr) {
23,547✔
938
                auto ls = this->lf_date_time.unlock();
21,156✔
939
                retval = this->lf_date_time.scan(
21,156✔
940
                    ts->data(), ts->length(), nullptr, tm_out, *tv_out);
21,156✔
941
                if (retval != nullptr) {
21,156✔
942
                    auto old_flags
×
943
                        = this->lf_timestamp_flags & DATE_TIME_SET_FLAGS;
×
944
                    auto new_flags = tm_out->et_flags & DATE_TIME_SET_FLAGS;
×
945

946
                    // It is unlikely a valid timestamp would lose much
947
                    // precision.
948
                    if (new_flags != old_flags) {
×
949
                        retval = nullptr;
×
950
                    }
951
                }
952
                if (retval == nullptr) {
21,156✔
953
                    this->lf_date_time.relock(ls);
21,156✔
954
                } else {
955
                    log_debug(
×
956
                        "%d: changed time format to '%s' due to %.*s",
957
                        line_number,
958
                        PTIMEC_FORMAT_STR[this->lf_date_time.dts_fmt_lock],
959
                        ts->length(),
960
                        ts->data());
961
                }
962
            }
963

964
            if (retval) {
23,547✔
965
                *ts_out = ts.value();
2,391✔
966
                if (md[2]) {
2,391✔
967
                    *level_out = md[2];
248✔
968
                } else {
969
                    *level_out = line.substr(md[0]->sf_end);
2,143✔
970
                }
971
                if (curr_fmt != pat_index) {
2,391✔
972
                    uint32_t lock_line;
973

974
                    if (sbc.sbc_pattern_locks.empty()) {
2,281✔
975
                        lock_line = 0;
2,281✔
976
                    } else {
977
                        lock_line = line_number;
×
978
                    }
979

980
                    sbc.sbc_pattern_locks.pl_lines.emplace_back(lock_line,
2,281✔
981
                                                                curr_fmt);
982
                }
983
                this->lf_timestamp_flags = tm_out->et_flags;
2,391✔
984
                done = true;
2,391✔
985
            }
986
        }
987
    }
988

989
    return retval;
12,373✔
990
}
991

992
void
993
log_format::annotate(logfile* lf,
37,550✔
994
                     uint64_t line_number,
995
                     string_attrs_t& sa,
996
                     logline_value_vector& values) const
997
{
998
    if (lf != nullptr && !values.lvv_opid_value) {
37,550✔
999
        const auto& bm = lf->get_bookmark_metadata();
2,904✔
1000
        auto bm_iter = bm.find(line_number);
2,904✔
1001
        if (bm_iter != bm.end() && !bm_iter->second.bm_opid.empty()) {
2,904✔
1002
            values.lvv_opid_value = bm_iter->second.bm_opid;
6✔
1003
            values.lvv_opid_provenance
1004
                = logline_value_vector::opid_provenance::user;
6✔
1005
        }
1006
    }
1007
}
37,550✔
1008

1009
void
1010
log_format::check_for_new_year(std::vector<logline>& dst,
1,482✔
1011
                               exttm etm,
1012
                               timeval log_tv) const
1013
{
1014
    if (dst.empty()) {
1,482✔
1015
        return;
215✔
1016
    }
1017

1018
    time_t diff
1019
        = dst.back().get_time<std::chrono::seconds>().count() - log_tv.tv_sec;
1,267✔
1020
    int off_year = 0, off_month = 0, off_day = 0, off_hour = 0;
1,267✔
1021
    bool do_change = true;
1,267✔
1022

1023
    if (diff <= 0) {
1,267✔
1024
        return;
1,207✔
1025
    }
1026
    if ((etm.et_flags & ETF_MONTH_SET) && diff >= (24 * 60 * 60)) {
60✔
1027
        off_year = 1;
11✔
1028
    } else if (diff >= (24 * 60 * 60)) {
49✔
1029
        off_month = 1;
2✔
1030
    } else if (!(etm.et_flags & ETF_DAY_SET) && (diff >= (60 * 60))) {
47✔
1031
        off_day = 1;
1✔
1032
    } else if (!(etm.et_flags & ETF_HOUR_SET) && (diff >= 60)) {
46✔
1033
        off_hour = 1;
4✔
1034
    } else {
1035
        do_change = false;
42✔
1036
    }
1037

1038
    if (!do_change) {
60✔
1039
        return;
42✔
1040
    }
1041
    log_debug("%zu:detected time rollover; offsets=%d %d %d %d",
18✔
1042
              dst.size(),
1043
              off_year,
1044
              off_month,
1045
              off_day,
1046
              off_hour);
1047
    for (auto& ll : dst) {
68✔
1048
        time_t ot = ll.get_time<std::chrono::seconds>().count();
50✔
1049
        struct tm otm;
1050

1051
        gmtime_r(&ot, &otm);
50✔
1052
        otm.tm_yday = -1;
50✔
1053
        if (otm.tm_year < off_year) {
50✔
1054
            otm.tm_year = 0;
×
1055
        } else {
1056
            otm.tm_year -= off_year;
50✔
1057
        }
1058
        otm.tm_mon -= off_month;
50✔
1059
        if (otm.tm_mon < 0) {
50✔
1060
            otm.tm_mon += 12;
2✔
1061
        }
1062
        auto new_time = tm2sec(&otm);
50✔
1063
        if (new_time == -1) {
50✔
1064
            continue;
×
1065
        }
1066
        new_time -= (off_day * 24 * 60 * 60) + (off_hour * 60 * 60);
50✔
1067
        auto old_sub = ll.get_subsecond_time<std::chrono::microseconds>();
50✔
1068
        ll.set_time(std::chrono::seconds{new_time});
50✔
1069
        ll.set_subsecond_time(old_sub);
50✔
1070
    }
1071
}
1072

1073
/*
1074
 * XXX This needs some cleanup.
1075
 */
1076
struct json_log_userdata {
1077
    json_log_userdata(shared_buffer_ref& sbr, scan_batch_context* sbc)
13,765✔
1078
        : jlu_shared_buffer(sbr), jlu_batch_context(sbc)
27,530✔
1079
    {
1080
    }
13,765✔
1081

1082
    const external_log_format::value_def* get_field_def(
337,765✔
1083
        yajlpp_parse_context* ypc)
1084
    {
1085
        const auto field_frag = ypc->get_path_as_string_fragment();
337,765✔
1086
        auto* format = this->jlu_format;
337,765✔
1087

1088
        if (this->jlu_read_order_index < format->elf_value_def_read_order.size()
337,765✔
1089
            && format->elf_value_def_read_order[this->jlu_read_order_index]
453,432✔
1090
                    .first
1091
                == field_frag)
115,667✔
1092
        {
1093
            return format
1094
                ->elf_value_def_read_order[this->jlu_read_order_index++]
110,659✔
1095
                .second;
110,659✔
1096
        }
1097

1098
        format->elf_value_def_read_order.resize(this->jlu_read_order_index);
227,106✔
1099
        auto vd_iter = format->elf_value_def_frag_map.find(field_frag);
227,106✔
1100
        if (vd_iter != format->elf_value_def_frag_map.end()) {
227,106✔
1101
            format->elf_value_def_read_order.emplace_back(vd_iter->first,
196,771✔
1102
                                                          vd_iter->second);
196,771✔
1103
            this->jlu_read_order_index += 1;
196,771✔
1104
            return vd_iter->second;
196,771✔
1105
        }
1106

1107
        auto owned_frag = field_frag.to_owned(format->elf_allocator);
30,335✔
1108
        format->elf_value_def_frag_map[owned_frag] = nullptr;
30,335✔
1109
        format->elf_value_def_read_order.emplace_back(owned_frag, nullptr);
30,335✔
1110
        this->jlu_read_order_index += 1;
30,335✔
1111
        return nullptr;
30,335✔
1112
    }
1113

1114
    void add_sub_lines_for(const external_log_format::value_def* vd,
246,648✔
1115
                           bool top_level,
1116
                           std::optional<double> val,
1117
                           const unsigned char* str,
1118
                           ssize_t len,
1119
                           yajl_string_props_t* props)
1120
    {
1121
        auto res = this->jlu_format->value_line_count(
246,648✔
1122
            *this->jlu_batch_context, vd, top_level, val, str, len, props);
246,648✔
1123
        this->jlu_has_ansi |= res.vlcr_has_ansi;
246,648✔
1124
        if (!res.vlcr_valid_utf) {
246,648✔
1125
            this->jlu_valid_utf = false;
×
1126
        }
1127
        this->jlu_sub_line_count += res.vlcr_count;
246,648✔
1128
        this->jlu_quality += res.vlcr_line_format_count;
246,648✔
1129
        if (res.vlcr_line_format_index) {
246,648✔
1130
            this->jlu_format_hits[res.vlcr_line_format_index.value()] = true;
3,264✔
1131
        }
1132
    }
246,648✔
1133

1134
    external_log_format* jlu_format{nullptr};
1135
    const logline* jlu_line{nullptr};
1136
    logline* jlu_base_line{nullptr};
1137
    int jlu_sub_line_count{1};
1138
    bool jlu_has_ansi{false};
1139
    bool jlu_valid_utf{true};
1140
    yajl_handle jlu_handle{nullptr};
1141
    const char* jlu_line_value{nullptr};
1142
    size_t jlu_line_size{0};
1143
    size_t jlu_sub_start{0};
1144
    uint32_t jlu_quality{0};
1145
    uint32_t jlu_strikes{0};
1146
    std::vector<bool> jlu_format_hits;
1147
    shared_buffer_ref& jlu_shared_buffer;
1148
    scan_batch_context* jlu_batch_context;
1149
    std::optional<string_fragment> jlu_opid_frag;
1150
    std::optional<string_fragment> jlu_opid_desc_frag;
1151
    std::optional<string_fragment> jlu_tid_frag;
1152
    std::optional<int64_t> jlu_tid_number;
1153
    std::optional<std::string> jlu_subid;
1154
    std::optional<log_format::scan_error> jlu_scan_error;
1155
    hasher jlu_opid_hasher;
1156
    std::chrono::microseconds jlu_duration{0};
13,765✔
1157
    exttm jlu_exttm;
1158
    size_t jlu_read_order_index{0};
1159
    subline_options jlu_subline_opts;
1160
};
1161

1162
static int read_json_field(yajlpp_parse_context* ypc,
1163
                           const unsigned char* str,
1164
                           size_t len,
1165
                           yajl_string_props_t*);
1166

1167
static int
1168
read_json_null(yajlpp_parse_context* ypc)
3,536✔
1169
{
1170
    json_log_userdata* jlu = (json_log_userdata*) ypc->ypc_userdata;
3,536✔
1171
    const auto* vd = jlu->get_field_def(ypc);
3,536✔
1172

1173
    jlu->add_sub_lines_for(
7,072✔
1174
        vd, ypc->is_level(1), std::nullopt, nullptr, -1, nullptr);
3,536✔
1175

1176
    return 1;
3,536✔
1177
}
1178

1179
static int
1180
read_json_bool(yajlpp_parse_context* ypc, int val)
16,832✔
1181
{
1182
    json_log_userdata* jlu = (json_log_userdata*) ypc->ypc_userdata;
16,832✔
1183
    const auto* vd = jlu->get_field_def(ypc);
16,832✔
1184

1185
    jlu->add_sub_lines_for(
33,664✔
1186
        vd, ypc->is_level(1), std::nullopt, nullptr, -1, nullptr);
16,832✔
1187

1188
    return 1;
16,832✔
1189
}
1190

1191
static int
1192
read_json_number(yajlpp_parse_context* ypc,
27,834✔
1193
                 const char* numberVal,
1194
                 size_t numberLen)
1195
{
1196
    auto* jlu = (json_log_userdata*) ypc->ypc_userdata;
27,834✔
1197
    auto number_frag = string_fragment::from_bytes(numberVal, numberLen);
27,834✔
1198
    std::optional<double> val;
27,834✔
1199

1200
    intern_string_t field_name;
27,834✔
1201
    const auto* vd = jlu->get_field_def(ypc);
27,834✔
1202
    if (vd != nullptr) {
27,834✔
1203
        field_name = vd->vd_meta.lvm_name;
1,626✔
1204
    }
1205

1206
    if (field_name.empty()) {
27,834✔
1207
    } else if (jlu->jlu_format->lf_timestamp_field == field_name) {
1,626✔
1208
        long long divisor = jlu->jlu_format->elf_timestamp_divisor;
113✔
1209
        auto scan_res = scn::scan_value<double>(number_frag.to_string_view());
113✔
1210
        if (!scan_res) {
113✔
1211
            log_error("invalid number %.*s", (int) numberLen, numberVal);
×
1212
            return 0;
×
1213
        }
1214
        auto ts_val = scan_res.value().value();
113✔
1215
        timeval tv;
1216
        tv.tv_sec = ts_val / divisor;
113✔
1217
        tv.tv_usec = fmod(ts_val, divisor) * (1000000.0 / divisor);
113✔
1218
        jlu->jlu_format->lf_date_time.to_localtime(tv.tv_sec, jlu->jlu_exttm);
113✔
1219
        tv.tv_sec = tm2sec(&jlu->jlu_exttm.et_tm);
113✔
1220
        jlu->jlu_exttm.et_gmtoff
1221
            = jlu->jlu_format->lf_date_time.dts_local_offset_cache;
113✔
1222
        jlu->jlu_exttm.et_flags
113✔
1223
            |= ETF_MACHINE_ORIENTED | ETF_SUB_NOT_IN_FORMAT | ETF_ZONE_SET;
113✔
1224
        if (divisor == 1000) {
113✔
1225
            jlu->jlu_exttm.et_flags |= ETF_MILLIS_SET;
17✔
1226
        } else {
1227
            jlu->jlu_exttm.et_flags |= ETF_MICROS_SET;
96✔
1228
        }
1229
        jlu->jlu_exttm.et_nsec = tv.tv_usec * 1000;
113✔
1230
        jlu->jlu_base_line->set_time(tv);
113✔
1231
    } else if (jlu->jlu_format->lf_subsecond_field == field_name) {
1,513✔
1232
        auto scan_res = scn::scan_value<double>(number_frag.to_string_view());
3✔
1233
        if (!scan_res) {
3✔
1234
            log_error("invalid number %.*s", (int) numberLen, numberVal);
×
1235
            return 0;
×
1236
        }
1237
        auto ts_val = scan_res.value().value();
3✔
1238

1239
        uint64_t millis = 0;
3✔
1240
        jlu->jlu_exttm.et_flags &= ~(ETF_MICROS_SET | ETF_MILLIS_SET);
3✔
1241
        switch (jlu->jlu_format->lf_subsecond_unit.value()) {
3✔
1242
            case log_format::subsecond_unit::milli:
×
1243
                millis = ts_val;
×
1244
                jlu->jlu_exttm.et_nsec = ts_val * 1000000;
×
1245
                jlu->jlu_exttm.et_flags |= ETF_MILLIS_SET;
×
1246
                break;
×
1247
            case log_format::subsecond_unit::micro:
×
1248
                millis = std::chrono::duration_cast<std::chrono::milliseconds>(
×
1249
                             std::chrono::microseconds((int64_t) ts_val))
×
1250
                             .count();
×
1251
                jlu->jlu_exttm.et_nsec = ts_val * 1000;
×
1252
                jlu->jlu_exttm.et_flags |= ETF_MICROS_SET;
×
1253
                break;
×
1254
            case log_format::subsecond_unit::nano:
3✔
1255
                millis = std::chrono::duration_cast<std::chrono::milliseconds>(
3✔
1256
                             std::chrono::nanoseconds((int64_t) ts_val))
3✔
1257
                             .count();
3✔
1258
                jlu->jlu_exttm.et_nsec = ts_val;
3✔
1259
                jlu->jlu_exttm.et_flags |= ETF_NANOS_SET;
3✔
1260
                break;
3✔
1261
        }
1262
        jlu->jlu_exttm.et_flags |= ETF_SUB_NOT_IN_FORMAT;
3✔
1263
        jlu->jlu_base_line->set_subsecond_time(
3✔
1264
            std::chrono::milliseconds(millis));
1265
    } else if (jlu->jlu_format->elf_level_field == field_name) {
1,510✔
1266
        if (jlu->jlu_format->elf_level_pairs.empty()) {
428✔
1267
            jlu->jlu_base_line->set_level(jlu->jlu_format->convert_level(
266✔
1268
                number_frag, jlu->jlu_batch_context));
1269
        } else {
1270
            auto scan_res
1271
                = scn::scan_int<int64_t>(number_frag.to_string_view());
162✔
1272
            if (!scan_res) {
162✔
1273
                log_error("invalid number %.*s", (int) numberLen, numberVal);
×
1274
                return 0;
×
1275
            }
1276
            auto level_int = scan_res.value().value();
162✔
1277

1278
            for (const auto& pair : jlu->jlu_format->elf_level_pairs) {
593✔
1279
                if (pair.first == level_int) {
531✔
1280
                    jlu->jlu_base_line->set_level(pair.second);
100✔
1281
                    break;
100✔
1282
                }
1283
            }
1284
        }
1285
    } else if (vd != nullptr) {
1,082✔
1286
        if (jlu->jlu_format->elf_thread_id_field == field_name) {
1,082✔
1287
            auto& sbc = *jlu->jlu_batch_context;
102✔
1288
            auto tid_iter = sbc.sbc_tids.ltis_tid_ranges.find(number_frag);
102✔
1289
            if (tid_iter == sbc.sbc_tids.ltis_tid_ranges.end()) {
102✔
1290
                jlu->jlu_tid_frag = number_frag.to_owned(sbc.sbc_allocator);
58✔
1291
            } else {
1292
                jlu->jlu_tid_frag = tid_iter->first;
44✔
1293
            }
1294
        }
1295
        if ((vd->vd_meta.lvm_kind == value_kind_t::VALUE_INTEGER
1,082✔
1296
             || vd->vd_meta.lvm_kind == value_kind_t::VALUE_FLOAT)
126✔
1297
            && !vd->vd_meta.lvm_foreign_key && !vd->vd_meta.lvm_identifier)
974✔
1298
        {
1299
            auto scan_res
1300
                = scn::scan_value<double>(number_frag.to_string_view());
168✔
1301
            if (!scan_res) {
168✔
1302
                log_error("invalid number %.*s", (int) numberLen, numberVal);
×
1303
                return 0;
×
1304
            }
1305
            val = scan_res.value().value();
168✔
1306
            if (jlu->jlu_format->elf_duration_field == field_name) {
168✔
1307
                jlu->jlu_duration = std::chrono::microseconds(
×
1308
                    static_cast<int64_t>(val.value() * 1000000));
18✔
1309
            }
1310
        }
1311
    }
1312

1313
    jlu->add_sub_lines_for(vd,
27,834✔
1314
                           ypc->is_level(1),
27,834✔
1315
                           val,
1316
                           (const unsigned char*) numberVal,
1317
                           numberLen,
1318
                           nullptr);
1319

1320
    return 1;
27,834✔
1321
}
1322

1323
static int
1324
json_array_start(void* ctx)
53,603✔
1325
{
1326
    auto* ypc = (yajlpp_parse_context*) ctx;
53,603✔
1327
    auto* jlu = (json_log_userdata*) ypc->ypc_userdata;
53,603✔
1328

1329
    if (ypc->ypc_path_index_stack.size() == 2) {
53,603✔
1330
        const auto* vd = jlu->get_field_def(ypc);
16,565✔
1331

1332
        jlu->add_sub_lines_for(vd, true, std::nullopt, nullptr, -1, nullptr);
16,565✔
1333
        jlu->jlu_sub_start = yajl_get_bytes_consumed(jlu->jlu_handle) - 1;
16,565✔
1334
    }
1335

1336
    return 1;
53,603✔
1337
}
1338

1339
static int
1340
json_array_start_const(void* ctx)
13,362✔
1341
{
1342
    auto* ypc = (yajlpp_parse_context*) ctx;
13,362✔
1343
    auto* jlu = (json_log_userdata*) ypc->ypc_userdata;
13,362✔
1344

1345
    if (ypc->ypc_path_index_stack.size() == 2) {
13,362✔
1346
        jlu->jlu_sub_start = yajl_get_bytes_consumed(jlu->jlu_handle) - 1;
3,579✔
1347
    }
1348

1349
    return 1;
13,362✔
1350
}
1351

1352
static int
1353
json_array_end(void* ctx)
13,342✔
1354
{
1355
    auto* ypc = (yajlpp_parse_context*) ctx;
13,342✔
1356
    auto* jlu = (json_log_userdata*) ypc->ypc_userdata;
13,342✔
1357

1358
    if (ypc->ypc_path_index_stack.size() == 1) {
13,342✔
1359
        const intern_string_t field_name = ypc->get_path_fragment_i(0);
3,573✔
1360
        size_t sub_end = yajl_get_bytes_consumed(jlu->jlu_handle);
3,573✔
1361
        auto json_frag = string_fragment::from_byte_range(
3,573✔
1362
            jlu->jlu_shared_buffer.get_data(), jlu->jlu_sub_start, sub_end);
3,573✔
1363
        jlu->jlu_format->jlf_line_values.lvv_values.emplace_back(
3,573✔
1364
            jlu->jlu_format->get_value_meta(field_name,
7,146✔
1365
                                            value_kind_t::VALUE_JSON),
1366
            json_frag);
1367
        if (field_name == jlu->jlu_format->elf_opid_field) {
3,573✔
1368
            jlu->jlu_opid_desc_frag = json_frag;
29✔
1369
        }
1370
    }
1371

1372
    return 1;
13,342✔
1373
}
1374

1375
static int
1376
read_array_end(void* ctx)
53,376✔
1377
{
1378
    auto* ypc = (yajlpp_parse_context*) ctx;
53,376✔
1379
    auto* jlu = (json_log_userdata*) ypc->ypc_userdata;
53,376✔
1380

1381
    if (ypc->ypc_path_index_stack.size() == 1) {
53,376✔
1382
        const intern_string_t field_name = ypc->get_path_fragment_i(0);
16,536✔
1383
        size_t sub_end = yajl_get_bytes_consumed(jlu->jlu_handle);
16,536✔
1384
        auto json_frag = string_fragment::from_byte_range(
16,536✔
1385
            jlu->jlu_shared_buffer.get_data(), jlu->jlu_sub_start, sub_end);
16,536✔
1386
        if (field_name == jlu->jlu_format->elf_opid_field) {
16,536✔
1387
            jlu->jlu_opid_desc_frag = json_frag;
2,286✔
1388
        }
1389
    }
1390

1391
    return 1;
53,376✔
1392
}
1393

1394
static const json_path_container json_log_handlers = {
1395
    yajlpp::pattern_property_handler("\\w+")
1396
        .add_cb(read_json_null)
1397
        .add_cb(read_json_bool)
1398
        .add_cb(read_json_number)
1399
        .add_cb(read_json_field),
1400
};
1401

1402
static int rewrite_json_field(yajlpp_parse_context* ypc,
1403
                              const unsigned char* str,
1404
                              size_t len,
1405
                              yajl_string_props_t*);
1406

1407
static int
1408
rewrite_json_null(yajlpp_parse_context* ypc)
1,957✔
1409
{
1410
    auto jlu = (json_log_userdata*) ypc->ypc_userdata;
1,957✔
1411
    const auto* vd = jlu->get_field_def(ypc);
1,957✔
1412

1413
    if (!ypc->is_level(1) && vd == nullptr) {
1,957✔
1414
        return 1;
1,957✔
1415
    }
1416
    jlu->jlu_format->jlf_line_values.lvv_values.emplace_back(
×
1417
        jlu->jlu_format->get_value_meta(ypc, vd, value_kind_t::VALUE_NULL));
×
1418

1419
    return 1;
×
1420
}
1421

1422
static int
1423
rewrite_json_bool(yajlpp_parse_context* ypc, int val)
8,797✔
1424
{
1425
    auto* jlu = (json_log_userdata*) ypc->ypc_userdata;
8,797✔
1426
    const auto* vd = jlu->get_field_def(ypc);
8,797✔
1427

1428
    if (!ypc->is_level(1) && vd == nullptr) {
8,797✔
1429
        return 1;
7,855✔
1430
    }
1431
    jlu->jlu_format->jlf_line_values.lvv_values.emplace_back(
942✔
1432
        jlu->jlu_format->get_value_meta(ypc, vd, value_kind_t::VALUE_BOOLEAN),
942✔
1433
        (bool) val);
942✔
1434
    return 1;
942✔
1435
}
1436

1437
static int
1438
rewrite_json_int(yajlpp_parse_context* ypc, long long val)
10,795✔
1439
{
1440
    auto* jlu = (json_log_userdata*) ypc->ypc_userdata;
10,795✔
1441
    const auto* vd = jlu->get_field_def(ypc);
10,795✔
1442

1443
    if (vd != nullptr) {
10,795✔
1444
        const intern_string_t field_name = vd->vd_meta.lvm_name;
797✔
1445
        if (jlu->jlu_format->lf_timestamp_field == field_name) {
797✔
1446
            long long divisor = jlu->jlu_format->elf_timestamp_divisor;
22✔
1447
            timeval tv;
1448

1449
            tv.tv_sec = val / divisor;
22✔
1450
            tv.tv_usec = fmod(val, divisor) * (1000000.0 / divisor);
22✔
1451
            jlu->jlu_format->lf_date_time.to_localtime(tv.tv_sec,
22✔
1452
                                                       jlu->jlu_exttm);
22✔
1453
            jlu->jlu_exttm.et_gmtoff
1454
                = jlu->jlu_format->lf_date_time.dts_local_offset_cache;
22✔
1455
            jlu->jlu_exttm.et_flags |= ETF_MACHINE_ORIENTED
22✔
1456
                | ETF_SUB_NOT_IN_FORMAT | ETF_ZONE_SET | ETF_Z_FOR_UTC;
1457
            if (divisor == 1) {
22✔
1458
                jlu->jlu_exttm.et_flags |= ETF_MICROS_SET;
4✔
1459
            } else {
1460
                jlu->jlu_exttm.et_flags |= ETF_MILLIS_SET;
18✔
1461
            }
1462
            jlu->jlu_exttm.et_nsec = tv.tv_usec * 1000;
22✔
1463
        } else if (jlu->jlu_format->lf_subsecond_field == field_name) {
775✔
1464
            jlu->jlu_exttm.et_flags &= ~(ETF_MICROS_SET | ETF_MILLIS_SET);
4✔
1465
            switch (jlu->jlu_format->lf_subsecond_unit.value()) {
4✔
1466
                case log_format::subsecond_unit::milli:
×
1467
                    jlu->jlu_exttm.et_nsec = val * 1000000;
×
1468
                    jlu->jlu_exttm.et_flags |= ETF_MILLIS_SET;
×
1469
                    break;
×
1470
                case log_format::subsecond_unit::micro:
×
1471
                    jlu->jlu_exttm.et_nsec = val * 1000;
×
1472
                    jlu->jlu_exttm.et_flags |= ETF_MICROS_SET;
×
1473
                    break;
×
1474
                case log_format::subsecond_unit::nano:
4✔
1475
                    jlu->jlu_exttm.et_nsec = val;
4✔
1476
                    jlu->jlu_exttm.et_flags |= ETF_NANOS_SET;
4✔
1477
                    break;
4✔
1478
            }
1479
            jlu->jlu_exttm.et_flags |= ETF_SUB_NOT_IN_FORMAT;
4✔
1480
        }
1481
    }
1482

1483
    if (!ypc->is_level(1) && vd == nullptr) {
10,795✔
1484
        return 1;
9,951✔
1485
    }
1486
    if (vd != nullptr
844✔
1487
        && vd->vd_meta.lvm_name == jlu->jlu_format->elf_thread_id_field)
844✔
1488
    {
1489
        jlu->jlu_tid_number = val;
140✔
1490
    }
1491
    jlu->jlu_format->jlf_line_values.lvv_values.emplace_back(
844✔
1492
        jlu->jlu_format->get_value_meta(ypc, vd, value_kind_t::VALUE_INTEGER),
844✔
1493
        (int64_t) val);
844✔
1494
    return 1;
844✔
1495
}
1496

1497
static int
1498
rewrite_json_double(yajlpp_parse_context* ypc, double val)
142✔
1499
{
1500
    auto* jlu = (json_log_userdata*) ypc->ypc_userdata;
142✔
1501
    const auto* vd = jlu->get_field_def(ypc);
142✔
1502

1503
    if (vd != nullptr) {
142✔
1504
        const intern_string_t field_name = vd->vd_meta.lvm_name;
138✔
1505
        if (jlu->jlu_format->lf_timestamp_field == field_name) {
138✔
1506
            long long divisor = jlu->jlu_format->elf_timestamp_divisor;
118✔
1507
            timeval tv;
1508

1509
            tv.tv_sec = val / divisor;
118✔
1510
            tv.tv_usec = fmod(val, divisor) * (1000000.0 / divisor);
118✔
1511
            jlu->jlu_format->lf_date_time.to_localtime(tv.tv_sec,
118✔
1512
                                                       jlu->jlu_exttm);
118✔
1513
            jlu->jlu_exttm.et_gmtoff
1514
                = jlu->jlu_format->lf_date_time.dts_local_offset_cache;
118✔
1515
            jlu->jlu_exttm.et_flags |= ETF_MACHINE_ORIENTED
118✔
1516
                | ETF_SUB_NOT_IN_FORMAT | ETF_ZONE_SET | ETF_Z_FOR_UTC;
1517
            if (divisor == 1) {
118✔
1518
                jlu->jlu_exttm.et_flags |= ETF_MICROS_SET;
110✔
1519
            } else {
1520
                jlu->jlu_exttm.et_flags |= ETF_MILLIS_SET;
8✔
1521
            }
1522
            jlu->jlu_exttm.et_nsec = tv.tv_usec * 1000;
118✔
1523
        } else if (jlu->jlu_format->lf_subsecond_field == field_name) {
20✔
1524
            jlu->jlu_exttm.et_flags &= ~(ETF_MICROS_SET | ETF_MILLIS_SET);
×
1525
            switch (jlu->jlu_format->lf_subsecond_unit.value()) {
×
1526
                case log_format::subsecond_unit::milli:
×
1527
                    jlu->jlu_exttm.et_nsec = val * 1000000;
×
1528
                    jlu->jlu_exttm.et_flags |= ETF_MILLIS_SET;
×
1529
                    break;
×
1530
                case log_format::subsecond_unit::micro:
×
1531
                    jlu->jlu_exttm.et_nsec = val * 1000;
×
1532
                    jlu->jlu_exttm.et_flags |= ETF_MICROS_SET;
×
1533
                    break;
×
1534
                case log_format::subsecond_unit::nano:
×
1535
                    jlu->jlu_exttm.et_nsec = val;
×
1536
                    jlu->jlu_exttm.et_flags |= ETF_NANOS_SET;
×
1537
                    break;
×
1538
            }
1539
            jlu->jlu_exttm.et_flags |= ETF_SUB_NOT_IN_FORMAT;
×
1540
        } else if (jlu->jlu_format->elf_duration_field == field_name) {
20✔
1541
            jlu->jlu_duration = std::chrono::microseconds(
40✔
1542
                static_cast<int64_t>(val * 1000000.0));
20✔
1543
        }
1544
    }
1545

1546
    if (!ypc->is_level(1) && vd == nullptr) {
142✔
1547
        return 1;
×
1548
    }
1549
    jlu->jlu_format->jlf_line_values.lvv_values.emplace_back(
142✔
1550
        jlu->jlu_format->get_value_meta(ypc, vd, value_kind_t::VALUE_FLOAT),
284✔
1551
        val);
1552

1553
    return 1;
142✔
1554
}
1555

1556
static const json_path_container json_log_rewrite_handlers = {
1557
    yajlpp::pattern_property_handler("\\w+")
1558
        .add_cb(rewrite_json_null)
1559
        .add_cb(rewrite_json_bool)
1560
        .add_cb(rewrite_json_int)
1561
        .add_cb(rewrite_json_double)
1562
        .add_cb(rewrite_json_field),
1563
};
1564

1565
bool
1566
external_log_format::scan_for_partial(const log_format_file_state& lffs,
1✔
1567
                                      shared_buffer_ref& sbr,
1568
                                      size_t& len_out) const
1569
{
1570
    if (this->elf_type != elf_type_t::ELF_TYPE_TEXT) {
1✔
1571
        return false;
×
1572
    }
1573

1574
    const auto& pat
1575
        = this->elf_pattern_order[lffs.lffs_pattern_locks.last_pattern_index()];
1✔
1576
    if (!this->lf_multiline) {
1✔
1577
        len_out = pat->p_pcre.pp_value->match_partial(sbr.to_string_fragment());
1✔
1578
        return true;
1✔
1579
    }
1580

1581
    if (pat->p_timestamp_end == -1 || pat->p_timestamp_end > (int) sbr.length())
×
1582
    {
1583
        len_out = 0;
×
1584
        return false;
×
1585
    }
1586

1587
    len_out = pat->p_pcre.pp_value->match_partial(sbr.to_string_fragment());
×
1588
    return (int) len_out > pat->p_timestamp_end;
×
1589
}
1590

1591
std::vector<lnav::console::snippet>
1592
external_log_format::get_snippets() const
23✔
1593
{
1594
    std::vector<lnav::console::snippet> retval;
23✔
1595

1596
    for (const auto& src_pair : this->elf_format_sources) {
46✔
1597
        retval.emplace_back(lnav::console::snippet::from(src_pair.first, "")
46✔
1598
                                .with_line(src_pair.second));
23✔
1599
    }
1600

1601
    return retval;
23✔
1602
}
×
1603

1604
log_format::scan_result_t
1605
external_log_format::scan_json(std::vector<logline>& dst,
164,610✔
1606
                               const line_info& li,
1607
                               shared_buffer_ref& sbr,
1608
                               scan_batch_context& sbc)
1609
{
1610
    logline ll(
1611
        li.li_file_range.fr_offset, std::chrono::microseconds{0}, LEVEL_INFO);
164,610✔
1612
    auto line_frag = sbr.to_string_fragment();
164,610✔
1613

1614
    if (!line_frag.startswith("{")) {
164,610✔
1615
        if (!this->lf_specialized) {
153,379✔
1616
            return scan_no_match{"line is not a JSON object"};
153,374✔
1617
        }
1618

1619
        if (!dst.empty()) {
5✔
1620
            ll.set_time(dst.back().get_time<std::chrono::microseconds>());
5✔
1621
        }
1622
        ll.set_level(LEVEL_INVALID);
5✔
1623
        dst.emplace_back(ll);
5✔
1624
        return scan_match{0};
5✔
1625
    }
1626

1627
    auto& ypc = *(this->jlf_parse_context);
11,231✔
1628
    yajl_handle handle = this->jlf_yajl_handle.get();
11,231✔
1629
    json_log_userdata jlu(sbr, &sbc);
11,231✔
1630

1631
    if (li.li_partial) {
11,231✔
1632
        log_debug("skipping partial line at offset %lld",
×
1633
                  li.li_file_range.fr_offset);
1634
        if (this->lf_specialized) {
×
1635
            if (!dst.empty()) {
×
1636
                ll.set_time(dst.back().get_time<std::chrono::microseconds>());
×
1637
            }
1638
            ll.set_level(LEVEL_INVALID);
×
1639
            dst.emplace_back(ll);
×
1640
        }
1641
        return scan_incomplete{};
×
1642
    }
1643

1644
    const auto* line_data = (const unsigned char*) sbr.get_data();
11,231✔
1645

1646
    this->lf_desc_captures.clear();
11,231✔
1647
    this->lf_desc_allocator.reset();
11,231✔
1648

1649
    yajl_reset(handle);
11,231✔
1650
    ypc.set_static_handler(json_log_handlers.jpc_children[0]);
11,231✔
1651
    ypc.ypc_userdata = &jlu;
11,231✔
1652
    ypc.ypc_ignore_unused = true;
11,231✔
1653
    ypc.ypc_alt_callbacks.yajl_start_array = json_array_start;
11,231✔
1654
    ypc.ypc_alt_callbacks.yajl_start_map = json_array_start;
11,231✔
1655
    ypc.ypc_alt_callbacks.yajl_end_array = read_array_end;
11,231✔
1656
    ypc.ypc_alt_callbacks.yajl_end_map = read_array_end;
11,231✔
1657
    jlu.jlu_format = this;
11,231✔
1658
    jlu.jlu_base_line = &ll;
11,231✔
1659
    jlu.jlu_line_value = sbr.get_data();
11,231✔
1660
    jlu.jlu_line_size = sbr.length();
11,231✔
1661
    jlu.jlu_handle = handle;
11,231✔
1662
    jlu.jlu_format_hits.resize(this->jlf_line_format.size());
11,231✔
1663
    if (yajl_parse(handle, line_data, sbr.length()) == yajl_status_ok
11,231✔
1664
        && yajl_complete_parse(handle) == yajl_status_ok)
11,231✔
1665
    {
1666
        if (jlu.jlu_scan_error) {
11,033✔
1667
            if (this->lf_specialized) {
19✔
1668
                if (!dst.empty()) {
10✔
1669
                    ll.set_time(
10✔
1670
                        dst.back().get_time<std::chrono::microseconds>());
10✔
1671
                }
1672
                ll.set_level(LEVEL_INVALID);
10✔
1673
                dst.emplace_back(ll);
10✔
1674
                return scan_match{0};
10✔
1675
            }
1676
            return jlu.jlu_scan_error.value();
9✔
1677
        }
1678
        if (ll.get_time<std::chrono::microseconds>().count() == 0) {
11,014✔
1679
            if (this->lf_specialized) {
7,001✔
1680
                if (!dst.empty()) {
×
1681
                    ll.set_time(
×
1682
                        dst.back().get_time<std::chrono::microseconds>());
×
1683
                }
1684
                ll.set_ignore(true);
×
1685
                dst.emplace_back(ll);
×
1686
                return scan_match{0};
×
1687
            }
1688

1689
            return scan_no_match{
7,001✔
1690
                "JSON message does not have expected timestamp property"};
7,001✔
1691
        }
1692

1693
        if (jlu.jlu_tid_frag) {
4,013✔
1694
            this->jlf_line_values.lvv_thread_id_value
1695
                = jlu.jlu_tid_frag->to_string();
102✔
1696
            auto tid_iter = sbc.sbc_tids.insert_tid(
204✔
1697
                sbc.sbc_allocator,
1698
                jlu.jlu_tid_frag.value(),
102✔
1699
                ll.get_time<std::chrono::microseconds>());
102✔
1700
            tid_iter->second.titr_level_stats.update_msg_count(
102✔
1701
                ll.get_msg_level());
1702
            ll.merge_bloom_bits(jlu.jlu_tid_frag->bloom_bits());
102✔
1703
        } else {
1704
            auto tid_iter = sbc.sbc_tids.insert_tid(
3,911✔
1705
                sbc.sbc_allocator,
1706
                string_fragment{},
×
1707
                ll.get_time<std::chrono::microseconds>());
3,911✔
1708
            tid_iter->second.titr_level_stats.update_msg_count(
3,911✔
1709
                ll.get_msg_level());
1710
        }
1711

1712
        auto found_opid_desc = false;
4,013✔
1713
        if (this->elf_opid_field.empty()
4,013✔
1714
            && this->lf_opid_source.value_or(opid_source_t::from_description)
760✔
1715
                == opid_source_t::from_description
1716
            && this->lf_opid_description_def->size() == 1)
4,773✔
1717
        {
1718
            const auto& od = this->lf_opid_description_def->begin()->second;
322✔
1719
            for (const auto& desc : *od.od_descriptors) {
966✔
1720
                auto desc_iter
1721
                    = this->lf_desc_captures.find(desc.od_field.pp_value);
644✔
1722
                if (desc_iter == this->lf_desc_captures.end()) {
644✔
1723
                    continue;
602✔
1724
                }
1725
                jlu.jlu_opid_hasher.update(desc_iter->second);
42✔
1726
                found_opid_desc = true;
42✔
1727
            }
1728

1729
        } else if (!jlu.jlu_opid_desc_frag && !jlu.jlu_opid_frag
5,096✔
1730
                   && jlu.jlu_duration > 0us)
5,096✔
1731
        {
1732
            jlu.jlu_opid_hasher.update(sbr.to_string_fragment());
×
1733
        }
1734

1735
        if (jlu.jlu_opid_desc_frag || jlu.jlu_duration > 0us
5,740✔
1736
            || (found_opid_desc && this->lf_opid_description_def->size() == 1))
5,740✔
1737
        {
1738
            char buf[hasher::STRING_SIZE];
1739
            jlu.jlu_opid_hasher.to_string(buf);
2,307✔
1740
            auto opid_frag = string_fragment::from_bytes(buf, sizeof(buf) - 1);
2,307✔
1741
            auto opid_iter = sbc.sbc_opids.los_opid_ranges.find(opid_frag);
2,307✔
1742
            if (opid_iter == sbc.sbc_opids.los_opid_ranges.end()) {
2,307✔
1743
                jlu.jlu_opid_frag = opid_frag.to_owned(sbc.sbc_allocator);
2,295✔
1744
            } else {
1745
                jlu.jlu_opid_frag = opid_iter->first;
12✔
1746
            }
1747
        }
1748

1749
        if (jlu.jlu_opid_frag) {
4,013✔
1750
            ll.merge_bloom_bits(jlu.jlu_opid_frag->bloom_bits());
3,229✔
1751
            this->jlf_line_values.lvv_opid_value
1752
                = jlu.jlu_opid_frag->to_string();
3,229✔
1753
            this->jlf_line_values.lvv_opid_provenance
1754
                = logline_value_vector::opid_provenance::file;
3,229✔
1755
            auto opid_iter = sbc.sbc_opids.insert_op(
6,458✔
1756
                sbc.sbc_allocator,
1757
                jlu.jlu_opid_frag.value(),
3,229✔
1758
                ll.get_time<std::chrono::microseconds>(),
3,229✔
1759
                this->lf_timestamp_point_of_reference,
1760
                jlu.jlu_duration);
1761
            opid_iter->second.otr_level_stats.update_msg_count(
3,229✔
1762
                ll.get_msg_level());
1763
            auto& elems = opid_iter->second.otr_description.lod_elements;
3,229✔
1764
            if (jlu.jlu_opid_desc_frag && elems.empty()) {
3,229✔
1765
                elems.insert(0,
×
1766
                             fmt::format(FMT_STRING(" {}"),
9,120✔
1767
                                         jlu.jlu_opid_desc_frag.value()));
1768
            }
1769

1770
            if (jlu.jlu_subid) {
3,229✔
1771
                auto subid_frag
1772
                    = string_fragment::from_str(jlu.jlu_subid.value());
×
1773

1774
                auto* ostr = sbc.sbc_opids.sub_op_in_use(
×
1775
                    sbc.sbc_allocator,
1776
                    opid_iter,
1777
                    subid_frag,
1778
                    ll.get_time<std::chrono::microseconds>(),
×
1779
                    ll.get_msg_level());
1780
                if (ostr != nullptr && ostr->ostr_description.empty()) {
×
1781
                    log_op_description sub_desc;
×
1782
                    this->update_op_description(
×
1783
                        *this->lf_subid_description_def_vec, sub_desc);
×
1784
                    if (!sub_desc.lod_elements.empty()) {
×
1785
                        auto& sub_desc_def
1786
                            = this->lf_subid_description_def_vec->at(
×
1787
                                sub_desc.lod_index.value());
×
1788
                        ostr->ostr_description
1789
                            = sub_desc_def->to_string(sub_desc.lod_elements);
×
1790
                    }
1791
                }
1792
            }
1793

1794
            auto& otr = opid_iter->second;
3,229✔
1795
            this->update_op_description(*this->lf_opid_description_def_vec,
3,229✔
1796
                                        otr.otr_description);
3,229✔
1797
        } else {
1798
            this->jlf_line_values.lvv_opid_value = std::nullopt;
784✔
1799
        }
1800

1801
        jlu.jlu_sub_line_count += this->jlf_line_format_init_count;
4,013✔
1802
        ll.set_has_ansi(jlu.jlu_has_ansi);
4,013✔
1803
        ll.set_valid_utf(jlu.jlu_valid_utf);
4,013✔
1804
        for (int lpc = 0; lpc < jlu.jlu_sub_line_count; lpc++) {
24,743✔
1805
            ll.set_sub_offset(lpc);
20,730✔
1806
            ll.set_continued(lpc > 0);
20,730✔
1807
            dst.emplace_back(ll);
20,730✔
1808
        }
1809
        this->lf_timestamp_flags = jlu.jlu_exttm.et_flags;
4,013✔
1810

1811
        if (!this->lf_specialized) {
4,013✔
1812
            static const intern_string_t ts_field
1813
                = intern_string::lookup("__timestamp__", -1);
3,480✔
1814
            static const intern_string_t level_field
1815
                = intern_string::lookup("__level__");
4,996✔
1816
            for (const auto& [index, jfe] :
32,373✔
1817
                 lnav::itertools::enumerate(this->jlf_line_format))
35,853✔
1818
            {
1819
                if (jfe.jfe_type != json_log_field::VARIABLE
77,120✔
1820
                    || jfe.jfe_value.pp_value == ts_field
18,749✔
1821
                    || jfe.jfe_value.pp_value == level_field
15,339✔
1822
                    || jfe.jfe_default_value != "-")
47,642✔
1823
                {
1824
                    continue;
19,334✔
1825
                }
1826
                if (!jlu.jlu_format_hits[index]) {
9,559✔
1827
                    jlu.jlu_strikes += 1;
9,076✔
1828
                }
1829
            }
1830
        }
1831
    } else {
1832
        unsigned char* msg;
1833
        int line_count = 2;
198✔
1834

1835
        msg = yajl_get_error(
396✔
1836
            handle, 1, (const unsigned char*) sbr.get_data(), sbr.length());
198✔
1837
        if (msg != nullptr) {
198✔
1838
            auto msg_frag = string_fragment::from_c_str(msg);
198✔
1839
            log_debug("Unable to parse line at offset %lld: %.*s",
198✔
1840
                      li.li_file_range.fr_offset,
1841
                      msg_frag.length(),
1842
                      msg_frag.data());
1843
            line_count = msg_frag.count('\n') + 1;
198✔
1844
            yajl_free_error(handle, msg);
198✔
1845
        }
1846
        if (!this->lf_specialized) {
198✔
1847
            return scan_no_match{"JSON parsing failed"};
195✔
1848
        }
1849
        for (int lpc = 0; lpc < line_count; lpc++) {
15✔
1850
            log_level_t level = LEVEL_INVALID;
12✔
1851

1852
            ll.set_time(dst.back().get_timeval());
12✔
1853
            ll.set_continued(lpc > 0);
12✔
1854
            ll.set_level(level);
12✔
1855
            ll.set_sub_offset(lpc);
12✔
1856
            dst.emplace_back(ll);
12✔
1857
        }
1858
    }
1859

1860
    if (jlu.jlu_quality > 0) {
4,016✔
1861
        jlu.jlu_quality += 3000;
834✔
1862
    }
1863
    return scan_match{jlu.jlu_quality, jlu.jlu_strikes};
4,016✔
1864
}
11,231✔
1865

1866
log_format::scan_result_t
1867
external_log_format::scan(logfile& lf,
853,787✔
1868
                          std::vector<logline>& dst,
1869
                          const line_info& li,
1870
                          shared_buffer_ref& sbr,
1871
                          scan_batch_context& sbc)
1872
{
1873
    if (dst.empty()) {
853,787✔
1874
        auto file_options = lf.get_file_options();
33,886✔
1875

1876
        if (file_options) {
33,886✔
1877
            this->lf_date_time.dts_default_zone
1878
                = file_options->second.fo_default_zone.pp_value;
2,328✔
1879
        } else {
1880
            this->lf_date_time.dts_default_zone = nullptr;
31,558✔
1881
        }
1882
    }
33,886✔
1883

1884
    sbc.sbc_value_stats.resize(this->elf_value_defs.size());
853,787✔
1885
    if (this->elf_type == elf_type_t::ELF_TYPE_JSON) {
853,787✔
1886
        return this->scan_json(dst, li, sbr, sbc);
161,576✔
1887
    }
1888

1889
    int curr_fmt = -1, orig_lock = sbc.sbc_pattern_locks.last_pattern_index();
692,211✔
1890
    int pat_index = orig_lock;
692,211✔
1891
    auto line_sf = sbr.to_string_fragment();
692,211✔
1892
    thread_local auto md = lnav::pcre2pp::match_data::unitialized();
692,211✔
1893
    char tmp_opid_buf[hasher::STRING_SIZE];
1894

1895
    while (::next_format(this->elf_pattern_order, curr_fmt, pat_index)) {
2,272,512✔
1896
        auto* fpat = this->elf_pattern_order[curr_fmt].get();
1,586,209✔
1897
        auto* pat = fpat->p_pcre.pp_value.get();
1,586,209✔
1898

1899
        auto found_match
1900
            = pat->capture_from(line_sf).into(md).found_p(PCRE2_NO_UTF_CHECK);
1,586,209✔
1901
        if (!found_match) {
1,586,209✔
1902
            if (!sbc.sbc_pattern_locks.empty() && pat_index != -1) {
1,580,298✔
1903
                curr_fmt = -1;
2,011✔
1904
                pat_index = -1;
2,011✔
1905
            }
1906
            continue;
1,580,301✔
1907
        }
1908

1909
        auto ts = md[fpat->p_timestamp_field_index];
5,911✔
1910
        auto level_cap = md[fpat->p_level_field_index];
5,911✔
1911
        auto opid_cap = md[fpat->p_opid_field_index];
5,911✔
1912
        const char* last;
1913
        exttm log_time_tm;
5,911✔
1914
        timeval log_tv;
1915
        uint64_t opid_bloom = 0;
5,911✔
1916
        char combined_datetime_buf[512];
1917

1918
        if (fpat->p_time_field_index != -1) {
5,911✔
1919
            auto time_cap = md[fpat->p_time_field_index];
×
1920
            if (ts && time_cap) {
×
1921
                auto ts_str_len = snprintf(combined_datetime_buf,
×
1922
                                           sizeof(combined_datetime_buf),
1923
                                           "%.*sT%.*s",
1924
                                           ts->length(),
1925
                                           ts->data(),
1926
                                           time_cap->length(),
1927
                                           time_cap->data());
1928
                ts = string_fragment::from_bytes(combined_datetime_buf,
×
1929
                                                 ts_str_len);
×
1930
            }
1931
        }
1932

1933
        auto level = this->convert_level(
5,911✔
1934
            level_cap.value_or(string_fragment::invalid()), &sbc);
5,911✔
1935

1936
        if (!ts) {
5,911✔
1937
            level = log_level_t::LEVEL_INVALID;
×
1938
        } else if ((last
5,911✔
1939
                    = this->lf_date_time.scan(ts->data(),
11,822✔
1940
                                              ts->length(),
5,911✔
1941
                                              this->get_timestamp_formats(),
1942
                                              &log_time_tm,
1943
                                              log_tv))
1944
                   == nullptr)
5,911✔
1945
        {
1946
            auto ls = this->lf_date_time.unlock();
13✔
1947
            if ((last = this->lf_date_time.scan(ts->data(),
26✔
1948
                                                ts->length(),
13✔
1949
                                                this->get_timestamp_formats(),
1950
                                                &log_time_tm,
1951
                                                log_tv))
1952
                == nullptr)
13✔
1953
            {
1954
                this->lf_date_time.relock(ls);
2✔
1955
                continue;
3✔
1956
            }
1957
            if (last != nullptr) {
11✔
1958
                auto old_flags = this->lf_timestamp_flags & DATE_TIME_SET_FLAGS;
11✔
1959
                auto new_flags = log_time_tm.et_flags & DATE_TIME_SET_FLAGS;
11✔
1960

1961
                // It is unlikely a valid timestamp would lose much
1962
                // precision.
1963
                if (new_flags != old_flags) {
11✔
1964
                    continue;
1✔
1965
                }
1966
            }
1967

1968
            log_debug("%s:%zu: date-time re-locked to %d",
10✔
1969
                      lf.get_unique_path().c_str(),
1970
                      dst.size(),
1971
                      this->lf_date_time.dts_fmt_lock);
1972
        }
1973

1974
        this->lf_timestamp_flags = log_time_tm.et_flags;
5,908✔
1975

1976
        if (!(this->lf_timestamp_flags
11,816✔
1977
              & (ETF_MILLIS_SET | ETF_MICROS_SET | ETF_NANOS_SET))
5,908✔
1978
            && !dst.empty()
5,320✔
1979
            && dst.back().get_time<std::chrono::seconds>().count()
4,566✔
1980
                == log_tv.tv_sec
4,566✔
1981
            && dst.back()
14,672✔
1982
                    .get_subsecond_time<std::chrono::milliseconds>()
9,352✔
1983
                    .count()
3,444✔
1984
                != 0)
1985
        {
1986
            auto log_ms
1987
                = dst.back().get_subsecond_time<std::chrono::microseconds>();
×
1988

1989
            log_time_tm.et_nsec
1990
                = std::chrono::duration_cast<std::chrono::nanoseconds>(log_ms)
×
1991
                      .count();
×
1992
            log_tv.tv_usec
1993
                = std::chrono::duration_cast<std::chrono::microseconds>(log_ms)
×
1994
                      .count();
×
1995
        }
1996

1997
        if (!((log_time_tm.et_flags & ETF_DAY_SET)
5,908✔
1998
              && (log_time_tm.et_flags & ETF_MONTH_SET)
5,867✔
1999
              && (log_time_tm.et_flags & ETF_YEAR_SET)))
5,867✔
2000
        {
2001
            this->check_for_new_year(dst, log_time_tm, log_tv);
794✔
2002
        }
2003

2004
        auto log_us = to_us(log_tv);
5,908✔
2005
        if (this->elf_opid_field.empty()
5,908✔
2006
            && !fpat->p_opid_description_field_indexes.empty())
5,908✔
2007
        {
2008
            auto empty_desc = true;
4,034✔
2009
            hasher h;
4,034✔
2010
            for (const auto& fidx : fpat->p_opid_description_field_indexes) {
12,102✔
2011
                auto desc_cap = md[fidx];
8,068✔
2012
                if (desc_cap) {
8,068✔
2013
                    h.update(desc_cap.value());
8,020✔
2014
                    empty_desc = false;
8,020✔
2015
                }
2016
            }
2017
            if (!empty_desc) {
4,034✔
2018
                h.to_string(tmp_opid_buf);
4,034✔
2019
                opid_cap = string_fragment::from_bytes(
8,068✔
2020
                    tmp_opid_buf, sizeof(tmp_opid_buf) - 1);
4,034✔
2021
            }
2022
        }
2023

2024
        auto duration_cap = md[fpat->p_duration_field_index];
5,908✔
2025
        if (duration_cap && !opid_cap) {
5,908✔
2026
            hasher h;
86✔
2027
            h.update(line_sf);
86✔
2028
            h.to_string(tmp_opid_buf);
86✔
2029
            opid_cap = string_fragment::from_bytes(tmp_opid_buf,
172✔
2030
                                                   sizeof(tmp_opid_buf) - 1);
86✔
2031
        }
2032
        if (opid_cap && !opid_cap->empty()) {
5,908✔
2033
            auto duration = std::chrono::microseconds{0};
4,919✔
2034
            if (duration_cap) {
4,919✔
2035
                auto from_res
2036
                    = humanize::try_from<double>(duration_cap.value());
86✔
2037
                if (from_res) {
86✔
2038
                    duration = std::chrono::microseconds(
×
2039
                        static_cast<int64_t>(from_res.value() * 1000000));
86✔
2040
                }
2041
            }
2042
            auto opid_iter
2043
                = sbc.sbc_opids.insert_op(sbc.sbc_allocator,
9,838✔
2044
                                          opid_cap.value(),
4,919✔
2045
                                          log_us,
2046
                                          this->lf_timestamp_point_of_reference,
2047
                                          duration);
2048
            auto& otr = opid_iter->second;
4,919✔
2049

2050
            otr.otr_level_stats.update_msg_count(level);
4,919✔
2051
            if (fpat->p_subid_field_index != -1) {
4,919✔
2052
                auto subid_cap = md[fpat->p_subid_field_index];
62✔
2053
                if (subid_cap && !subid_cap->empty()) {
62✔
2054
                    auto* ostr = sbc.sbc_opids.sub_op_in_use(sbc.sbc_allocator,
186✔
2055
                                                             opid_iter,
2056
                                                             subid_cap.value(),
62✔
2057
                                                             log_us,
2058
                                                             level);
2059
                    if (ostr != nullptr && ostr->ostr_description.empty()) {
62✔
2060
                        log_op_description sub_desc;
41✔
2061
                        this->update_op_description(
41✔
2062
                            *this->lf_subid_description_def_vec,
41✔
2063
                            sub_desc,
2064
                            fpat,
2065
                            md);
2066
                        if (!sub_desc.lod_elements.empty()) {
41✔
2067
                            auto& sub_desc_def
2068
                                = this->lf_subid_description_def_vec->at(
39✔
2069
                                    sub_desc.lod_index.value());
39✔
2070
                            ostr->ostr_description = sub_desc_def->to_string(
78✔
2071
                                sub_desc.lod_elements);
39✔
2072
                        }
2073
                    }
41✔
2074
                }
2075
            }
2076
            this->update_op_description(*this->lf_opid_description_def_vec,
4,919✔
2077
                                        otr.otr_description,
4,919✔
2078
                                        fpat,
2079
                                        md);
2080
            opid_bloom = opid_cap->bloom_bits();
4,919✔
2081
        }
2082

2083
        for (const auto& ivd : fpat->p_value_by_index) {
66,782✔
2084
            if (!ivd.ivd_value_def->vd_meta.lvm_values_index) {
60,874✔
2085
                continue;
11,114✔
2086
            }
2087

2088
            ssize_t cap_size = md.capture_size(ivd.ivd_index);
49,760✔
2089
            auto& lvs = sbc.sbc_value_stats[ivd.ivd_value_def->vd_meta
49,760✔
2090
                                                .lvm_values_index.value()];
49,760✔
2091

2092
            if (cap_size > lvs.lvs_width) {
49,760✔
2093
                lvs.lvs_width = cap_size;
6,444✔
2094
            }
2095
        }
2096

2097
        for (auto value_index : fpat->p_numeric_value_indexes) {
10,782✔
2098
            const indexed_value_def& ivd = fpat->p_value_by_index[value_index];
4,874✔
2099
            const value_def& vd = *ivd.ivd_value_def;
4,874✔
2100
            auto num_cap = md[ivd.ivd_index];
4,874✔
2101

2102
            if (vd.vd_meta.lvm_identifier) {
4,874✔
2103
                continue;
×
2104
            }
2105

2106
            if (num_cap && num_cap->is_valid()) {
4,874✔
2107
                const scaling_factor* scaling = nullptr;
4,808✔
2108

2109
                if (ivd.ivd_unit_field_index >= 0) {
4,808✔
2110
                    auto unit_cap = md[ivd.ivd_unit_field_index];
80✔
2111

2112
                    if (unit_cap && unit_cap->is_valid()) {
80✔
2113
                        intern_string_t unit_val
2114
                            = intern_string::lookup(unit_cap.value());
80✔
2115

2116
                        auto unit_iter = vd.vd_unit_scaling.find(unit_val);
80✔
2117
                        if (unit_iter != vd.vd_unit_scaling.end()) {
80✔
2118
                            const auto& sf = unit_iter->second;
80✔
2119

2120
                            scaling = &sf;
80✔
2121
                        }
2122
                    }
2123
                }
2124

2125
                std::optional<double> dvalue_opt;
4,808✔
2126
                switch (vd.vd_meta.lvm_kind) {
4,808✔
2127
                    case value_kind_t::VALUE_INTEGER: {
4,642✔
2128
                        auto scan_res
2129
                            = scn::scan_int<int64_t>(num_cap->to_string_view());
4,642✔
2130
                        if (scan_res) {
4,642✔
2131
                            dvalue_opt = scan_res->value();
4,642✔
2132
                        }
2133
                        break;
4,642✔
2134
                    }
2135
                    case value_kind_t::VALUE_FLOAT: {
166✔
2136
                        auto scan_res = scn::scan_value<double>(
2137
                            num_cap->to_string_view());
166✔
2138
                        if (scan_res) {
166✔
2139
                            dvalue_opt = scan_res->value();
166✔
2140
                        }
2141
                        break;
166✔
2142
                    }
2143
                    default:
×
2144
                        break;
×
2145
                }
2146
                if (dvalue_opt) {
4,808✔
2147
                    auto dvalue = dvalue_opt.value();
4,808✔
2148
                    if (scaling != nullptr) {
4,808✔
2149
                        scaling->scale(dvalue);
80✔
2150
                    }
2151
                    sbc.sbc_value_stats[vd.vd_meta.lvm_values_index.value()]
4,808✔
2152
                        .add_value(dvalue);
4,808✔
2153
                }
2154
            }
2155
        }
2156

2157
        dst.emplace_back(li.li_file_range.fr_offset, log_us, level);
5,908✔
2158
        auto& new_line = dst.back();
5,908✔
2159
        new_line.merge_bloom_bits(opid_bloom);
5,908✔
2160

2161
        auto src_file_cap = md[fpat->p_src_file_field_index];
5,908✔
2162
        auto src_line_cap = md[fpat->p_src_line_field_index];
5,908✔
2163
        if (src_file_cap && src_line_cap) {
5,908✔
2164
            auto h = hasher();
94✔
2165
            h.update(this->get_name().c_str());
94✔
2166
            h.update(src_file_cap.value());
94✔
2167
            h.update(src_line_cap.value());
94✔
2168
            new_line.merge_bloom_bits(h.to_bloom_bits());
94✔
2169
            new_line.set_schema_computed(true);
94✔
2170
        }
2171
        auto thread_id_cap = md[fpat->p_thread_id_field_index];
5,908✔
2172
        if (thread_id_cap) {
5,908✔
2173
            auto tid_iter = sbc.sbc_tids.insert_tid(
2,090✔
2174
                sbc.sbc_allocator, thread_id_cap.value(), log_us);
1,045✔
2175
            tid_iter->second.titr_level_stats.update_msg_count(level);
1,045✔
2176
            new_line.merge_bloom_bits(thread_id_cap->bloom_bits());
1,045✔
2177
        } else {
2178
            auto tid_iter = sbc.sbc_tids.insert_tid(
4,863✔
2179
                sbc.sbc_allocator, string_fragment{}, log_us);
×
2180
            tid_iter->second.titr_level_stats.update_msg_count(level);
4,863✔
2181
        }
2182

2183
        if (orig_lock != curr_fmt) {
5,908✔
2184
            uint32_t lock_line;
2185

2186
            if (!this->lf_specialized && orig_lock != -1) {
626✔
2187
                log_debug("%s:%zu: changing pattern lock %d -> (%d)%s",
×
2188
                          lf.get_unique_path().c_str(),
2189
                          dst.size() - 1,
2190
                          orig_lock,
2191
                          curr_fmt,
2192
                          this->elf_pattern_order[curr_fmt]->p_name.c_str());
2193
            }
2194
            if (sbc.sbc_pattern_locks.empty()) {
626✔
2195
                lock_line = 0;
600✔
2196
            } else {
2197
                lock_line = dst.size() - 1;
26✔
2198
            }
2199
            sbc.sbc_pattern_locks.pl_lines.emplace_back(lock_line, curr_fmt);
626✔
2200
        }
2201
        return scan_match{1000};
5,908✔
2202
    }
2203

2204
    if (this->lf_specialized && !this->lf_multiline && !dst.empty()) {
686,303✔
2205
        const auto& last_line = dst.back();
1✔
2206

2207
        log_debug("%s: invalid line %zu file_offset=%" PRIu64,
1✔
2208
                  lf.get_filename().c_str(),
2209
                  dst.size(),
2210
                  li.li_file_range.fr_offset);
2211
        dst.emplace_back(li.li_file_range.fr_offset,
1✔
2212
                         last_line.get_time<std::chrono::microseconds>(),
×
2213
                         log_level_t::LEVEL_INVALID);
1✔
2214

2215
        return scan_match{0};
1✔
2216
    }
2217

2218
    return scan_no_match{"no patterns matched"};
686,302✔
2219
}
2220

2221
void
2222
external_log_format::annotate(logfile* lf,
7,420✔
2223
                              uint64_t line_number,
2224
                              string_attrs_t& sa,
2225
                              logline_value_vector& values) const
2226
{
2227
    thread_local auto md = lnav::pcre2pp::match_data::unitialized();
7,420✔
2228

2229
    auto& line = values.lvv_sbr;
7,420✔
2230
    line_range lr;
7,420✔
2231

2232
    line.erase_ansi();
7,420✔
2233
    if (this->elf_type != elf_type_t::ELF_TYPE_TEXT) {
7,420✔
2234
        if (this->jlf_cached_opts.full_message) {
759✔
2235
            values = this->jlf_line_values;
326✔
2236
            sa = this->jlf_line_attrs;
326✔
2237
        } else {
2238
            values.lvv_sbr = this->jlf_line_values.lvv_sbr.clone();
433✔
2239
            for (const auto& llv : this->jlf_line_values.lvv_values) {
4,276✔
2240
                if (this->jlf_cached_sub_range.contains(llv.lv_origin)) {
3,843✔
2241
                    values.lvv_values.emplace_back(llv);
911✔
2242
                    values.lvv_values.back().lv_origin.shift(
911✔
2243
                        this->jlf_cached_sub_range.lr_start,
911✔
2244
                        -this->jlf_cached_sub_range.lr_start);
911✔
2245
                }
2246
            }
2247
            for (const auto& attr : this->jlf_line_attrs) {
2,440✔
2248
                if (this->jlf_cached_sub_range.contains(attr.sa_range)) {
2,007✔
2249
                    sa.emplace_back(attr);
704✔
2250
                    sa.back().sa_range.shift(
704✔
2251
                        this->jlf_cached_sub_range.lr_start,
704✔
2252
                        -this->jlf_cached_sub_range.lr_start);
704✔
2253
                }
2254
            }
2255
            values.lvv_opid_value = this->jlf_line_values.lvv_opid_value;
433✔
2256
            values.lvv_opid_provenance
2257
                = this->jlf_line_values.lvv_opid_provenance;
433✔
2258
            values.lvv_thread_id_value
2259
                = this->jlf_line_values.lvv_thread_id_value;
433✔
2260
        }
2261
        log_format::annotate(lf, line_number, sa, values);
759✔
2262
        return;
2,376✔
2263
    }
2264

2265
    if (line.empty()) {
6,661✔
2266
        return;
5✔
2267
    }
2268

2269
    values.lvv_values.reserve(this->elf_value_defs.size());
6,656✔
2270

2271
    auto lffs = lf->get_format_file_state();
6,656✔
2272
    int pat_index = lffs.lffs_pattern_locks.pattern_index_for_line(line_number);
6,656✔
2273
    const auto& pat = *this->elf_pattern_order[pat_index];
6,656✔
2274
    char tmp_opid_buf[hasher::STRING_SIZE];
2275

2276
    sa.reserve(pat.p_pcre.pp_value->get_capture_count());
6,656✔
2277
    auto match_res
2278
        = pat.p_pcre.pp_value->capture_from(line.to_string_fragment())
6,656✔
2279
              .into(md)
6,656✔
2280
              .matches(PCRE2_NO_UTF_CHECK)
13,312✔
2281
              .ignore_error();
6,656✔
2282
    if (!match_res) {
6,656✔
2283
        // A continued line still needs a body.
2284
        lr.lr_start = 0;
1,612✔
2285
        lr.lr_end = line.length();
1,612✔
2286
        sa.emplace_back(lr, SA_BODY.value());
1,612✔
2287
        if (!this->lf_multiline) {
1,612✔
2288
            auto len
2289
                = pat.p_pcre.pp_value->match_partial(line.to_string_fragment());
×
2290
            sa.emplace_back(
×
2291
                line_range{(int) len, -1},
×
2292
                SA_INVALID.value("Log line does not match any pattern"));
×
2293
        }
2294
        return;
1,612✔
2295
    }
2296

2297
    auto duration_cap = md[pat.p_duration_field_index];
5,044✔
2298

2299
    auto ts_cap = md[pat.p_timestamp_field_index];
5,044✔
2300
    if (ts_cap) {
5,044✔
2301
        sa.emplace_back(to_line_range(ts_cap.value()), L_TIMESTAMP.value());
5,044✔
2302
    }
2303

2304
    auto opid_cap = md[pat.p_opid_field_index];
5,044✔
2305

2306
    if (this->elf_opid_field.empty()
5,044✔
2307
        && !pat.p_opid_description_field_indexes.empty())
5,044✔
2308
    {
2309
        auto empty_desc = true;
3,784✔
2310
        hasher h;
3,784✔
2311
        for (const auto& fidx : pat.p_opid_description_field_indexes) {
11,352✔
2312
            auto desc_cap = md[fidx];
7,568✔
2313
            if (desc_cap) {
7,568✔
2314
                h.update(desc_cap.value());
7,540✔
2315
                empty_desc = false;
7,540✔
2316
            }
2317
        }
2318
        if (!empty_desc) {
3,784✔
2319
            h.to_string(tmp_opid_buf);
3,784✔
2320
            opid_cap = string_fragment::from_bytes(tmp_opid_buf,
7,568✔
2321
                                                   sizeof(tmp_opid_buf) - 1);
3,784✔
2322
        }
2323
    } else if (duration_cap && !opid_cap) {
1,260✔
2324
        hasher h;
3✔
2325
        h.update(line.to_string_fragment());
3✔
2326
        h.to_string(tmp_opid_buf);
3✔
2327
        opid_cap = string_fragment::from_bytes(tmp_opid_buf,
6✔
2328
                                               sizeof(tmp_opid_buf) - 1);
3✔
2329
    }
2330
    if (opid_cap && !opid_cap->empty()) {
5,044✔
2331
        sa.emplace_back(to_line_range(opid_cap.value()), L_OPID.value());
4,239✔
2332
        values.lvv_opid_value = opid_cap->to_string();
4,239✔
2333
        values.lvv_opid_provenance
2334
            = logline_value_vector::opid_provenance::file;
4,239✔
2335
    }
2336

2337
    auto body_cap = md[pat.p_body_field_index];
5,044✔
2338
    auto level_cap = md[pat.p_level_field_index];
5,044✔
2339
    auto src_file_cap = md[pat.p_src_file_field_index];
5,044✔
2340
    auto src_line_cap = md[pat.p_src_line_field_index];
5,044✔
2341
    auto thread_id_cap = md[pat.p_thread_id_field_index];
5,044✔
2342

2343
    if (level_cap
5,044✔
2344
        && (!body_cap
10,042✔
2345
            || (body_cap && level_cap->sf_begin != body_cap->sf_begin)))
10,042✔
2346
    {
2347
        sa.emplace_back(to_line_range(level_cap.value()), L_LEVEL.value());
4,598✔
2348
    }
2349

2350
    if (src_file_cap) {
5,044✔
2351
        sa.emplace_back(to_line_range(src_file_cap.value()),
97✔
2352
                        SA_SRC_FILE.value());
194✔
2353
    }
2354
    if (src_line_cap) {
5,044✔
2355
        sa.emplace_back(to_line_range(src_line_cap.value()),
97✔
2356
                        SA_SRC_LINE.value());
194✔
2357
    }
2358
    if (thread_id_cap) {
5,044✔
2359
        sa.emplace_back(to_line_range(thread_id_cap.value()),
614✔
2360
                        SA_THREAD_ID.value());
1,228✔
2361
        values.lvv_thread_id_value = thread_id_cap->to_string();
614✔
2362
    }
2363
    if (duration_cap) {
5,044✔
2364
        sa.emplace_back(to_line_range(duration_cap.value()),
3✔
2365
                        SA_DURATION.value());
6✔
2366
    }
2367

2368
    for (size_t lpc = 0; lpc < pat.p_value_by_index.size(); lpc++) {
57,805✔
2369
        const auto& ivd = pat.p_value_by_index[lpc];
52,761✔
2370
        const scaling_factor* scaling = nullptr;
52,761✔
2371
        auto cap = md[ivd.ivd_index];
52,761✔
2372
        const auto& vd = *ivd.ivd_value_def;
52,761✔
2373

2374
        if (ivd.ivd_unit_field_index >= 0) {
52,761✔
2375
            auto unit_cap = md[ivd.ivd_unit_field_index];
10✔
2376

2377
            if (unit_cap) {
10✔
2378
                intern_string_t unit_val
2379
                    = intern_string::lookup(unit_cap.value());
10✔
2380
                auto unit_iter = vd.vd_unit_scaling.find(unit_val);
10✔
2381
                if (unit_iter != vd.vd_unit_scaling.end()) {
10✔
2382
                    const auto& sf = unit_iter->second;
10✔
2383

2384
                    scaling = &sf;
10✔
2385
                }
2386
            }
2387
        }
2388

2389
        if (cap) {
52,761✔
2390
            values.lvv_values.emplace_back(
86,084✔
2391
                vd.vd_meta, line, to_line_range(cap.value()));
43,042✔
2392
            values.lvv_values.back().apply_scaling(scaling);
43,042✔
2393
        } else {
2394
            values.lvv_values.emplace_back(vd.vd_meta);
9,719✔
2395
        }
2396
    }
2397

2398
    if (body_cap && body_cap->is_valid()) {
5,044✔
2399
        lr = to_line_range(body_cap.value());
5,032✔
2400
    } else {
2401
        lr.lr_start = line.length();
12✔
2402
        lr.lr_end = line.length();
12✔
2403
    }
2404
    sa.emplace_back(lr, SA_BODY.value());
5,044✔
2405

2406
    log_format::annotate(lf, line_number, sa, values);
5,044✔
2407
}
2408

2409
void
2410
external_log_format::rewrite(exec_context& ec,
43✔
2411
                             shared_buffer_ref& line,
2412
                             string_attrs_t& sa,
2413
                             std::string& value_out)
2414
{
2415
    auto pg = ec.with_provenance(exec_context::format_rewrite{});
43✔
2416
    auto& values = *ec.ec_line_values;
43✔
2417

2418
    value_out.assign(line.get_data(), line.length());
43✔
2419

2420
    for (auto iter = values.lvv_values.begin(); iter != values.lvv_values.end();
251✔
2421
         ++iter)
208✔
2422
    {
2423
        if (!iter->lv_origin.is_valid()) {
208✔
2424
            log_debug("%d: not rewriting value with invalid origin -- %s",
22✔
2425
                      (int) ec.ec_top_line,
2426
                      iter->lv_meta.lvm_name.get());
2427
            continue;
178✔
2428
        }
2429

2430
        auto vd_iter = this->elf_value_defs.find(iter->lv_meta.lvm_name);
186✔
2431
        if (vd_iter == this->elf_value_defs.end()) {
186✔
2432
            log_debug("%d: not rewriting undefined value -- %s",
×
2433
                      (int) ec.ec_top_line,
2434
                      iter->lv_meta.lvm_name.get());
2435
            continue;
×
2436
        }
2437

2438
        const auto& vd = *vd_iter->second;
186✔
2439

2440
        if (vd.vd_rewriter.empty()) {
186✔
2441
            continue;
156✔
2442
        }
2443

2444
        auto _sg = ec.enter_source(
2445
            vd_iter->second->vd_rewrite_src_name, 1, vd.vd_rewriter);
30✔
2446
        std::string field_value;
30✔
2447

2448
        auto_mem<FILE> tmpout(fclose);
30✔
2449

2450
        tmpout = std::tmpfile();
30✔
2451
        if (!tmpout) {
30✔
2452
            log_error("unable to create temporary file");
×
2453
            return;
×
2454
        }
2455
        fcntl(fileno(tmpout), F_SETFD, FD_CLOEXEC);
30✔
2456
        auto fd_copy = auto_fd::dup_of(fileno(tmpout));
30✔
2457
        fd_copy.close_on_exec();
30✔
2458
        auto ec_out = std::make_pair(tmpout.release(), fclose);
30✔
2459
        {
2460
            exec_context::output_guard og(ec, "tmp", ec_out);
60✔
2461

2462
            auto exec_res = execute_any(ec, vd.vd_rewriter);
30✔
2463
            if (exec_res.isOk()) {
30✔
2464
                field_value = exec_res.unwrap();
30✔
2465
            } else {
2466
                field_value = exec_res.unwrapErr().to_attr_line().get_string();
×
2467
            }
2468
        }
30✔
2469
        struct stat st;
2470
        fstat(fd_copy.get(), &st);
30✔
2471
        if (st.st_size > 0) {
30✔
2472
            auto buf = auto_buffer::alloc(st.st_size);
2✔
2473

2474
            buf.resize(st.st_size);
2✔
2475
            pread(fd_copy.get(), buf.in(), st.st_size, 0);
2✔
2476
            field_value = buf.to_string();
2✔
2477
        }
2✔
2478
        value_out.erase(iter->lv_origin.lr_start, iter->lv_origin.length());
30✔
2479

2480
        int32_t shift_amount
2481
            = ((int32_t) field_value.length()) - iter->lv_origin.length();
30✔
2482
        auto orig_lr = iter->lv_origin;
30✔
2483
        value_out.insert(iter->lv_origin.lr_start, field_value);
30✔
2484
        for (auto shift_iter = values.lvv_values.begin();
30✔
2485
             shift_iter != values.lvv_values.end();
170✔
2486
             ++shift_iter)
140✔
2487
        {
2488
            shift_iter->lv_origin.shift_range(orig_lr, shift_amount);
140✔
2489
        }
2490
        shift_string_attrs(sa, orig_lr, shift_amount);
30✔
2491
    }
30✔
2492
}
43✔
2493

2494
static int
2495
read_json_field(yajlpp_parse_context* ypc,
181,881✔
2496
                const unsigned char* str,
2497
                size_t len,
2498
                yajl_string_props_t* props)
2499
{
2500
    auto* jlu = (json_log_userdata*) ypc->ypc_userdata;
181,881✔
2501
    timeval tv_out;
2502
    const auto frag = string_fragment::from_bytes(str, len);
181,881✔
2503
    intern_string_t field_name;
181,881✔
2504
    const auto* vd = jlu->get_field_def(ypc);
181,881✔
2505

2506
    if (vd != nullptr) {
181,881✔
2507
        field_name = vd->vd_meta.lvm_name;
19,353✔
2508
    }
2509

2510
    if (field_name.empty()) {
181,881✔
2511
        if (!jlu->jlu_format->elf_opid_field.empty()) {
162,528✔
2512
            auto path_sf = ypc->get_path_as_string_fragment();
58,561✔
2513
            if (path_sf.startswith(jlu->jlu_format->elf_opid_field.c_str())) {
58,561✔
2514
                jlu->jlu_opid_hasher.update(path_sf);
9,117✔
2515
                jlu->jlu_opid_hasher.update(frag);
9,117✔
2516
            }
2517
        }
2518
    } else if (jlu->jlu_format->lf_timestamp_field == field_name) {
19,353✔
2519
        const auto* last = jlu->jlu_format->lf_date_time.scan(
3,925✔
2520
            (const char*) str,
2521
            len,
2522
            jlu->jlu_format->get_timestamp_formats(),
3,925✔
2523
            &jlu->jlu_exttm,
2524
            tv_out);
2525
        if (last == nullptr) {
3,925✔
2526
            auto ls = jlu->jlu_format->lf_date_time.unlock();
41✔
2527
            if ((last = jlu->jlu_format->lf_date_time.scan(
41✔
2528
                     (const char*) str,
2529
                     len,
2530
                     jlu->jlu_format->get_timestamp_formats(),
41✔
2531
                     &jlu->jlu_exttm,
2532
                     tv_out))
2533
                == nullptr)
41✔
2534
            {
2535
                jlu->jlu_format->lf_date_time.relock(ls);
19✔
2536
            }
2537
            if (last != nullptr) {
41✔
2538
                auto old_flags
22✔
2539
                    = jlu->jlu_format->lf_timestamp_flags & DATE_TIME_SET_FLAGS;
22✔
2540
                auto new_flags = jlu->jlu_exttm.et_flags & DATE_TIME_SET_FLAGS;
22✔
2541

2542
                // It is unlikely a valid timestamp would lose much
2543
                // precision.
2544
                if (new_flags != old_flags) {
22✔
2545
                    last = nullptr;
×
2546
                }
2547
            }
2548
        }
2549
        if (last != nullptr) {
3,925✔
2550
            jlu->jlu_format->lf_timestamp_flags = jlu->jlu_exttm.et_flags;
3,906✔
2551
            jlu->jlu_base_line->set_time(tv_out);
3,906✔
2552
        } else {
2553
            jlu->jlu_scan_error = log_format::scan_error{fmt::format(
19✔
2554
                "failed to parse timestamp '{}' in string property '{}'",
2555
                frag,
2556
                field_name)};
19✔
2557
        }
2558
    } else if (jlu->jlu_format->elf_level_pointer.pp_value != nullptr) {
15,428✔
2559
        if (jlu->jlu_format->elf_level_pointer.pp_value
230✔
2560
                ->find_in(field_name.to_string_fragment(), PCRE2_NO_UTF_CHECK)
230✔
2561
                .ignore_error()
230✔
2562
                .has_value())
115✔
2563
        {
2564
            jlu->jlu_base_line->set_level(
×
2565
                jlu->jlu_format->convert_level(frag, jlu->jlu_batch_context));
×
2566
        }
2567
    }
2568
    if (!field_name.empty() && jlu->jlu_format->elf_level_field == field_name) {
181,881✔
2569
        jlu->jlu_base_line->set_level(
4,175✔
2570
            jlu->jlu_format->convert_level(frag, jlu->jlu_batch_context));
4,175✔
2571
    }
2572
    if (!field_name.empty() && jlu->jlu_format->elf_opid_field == field_name) {
181,881✔
2573
        jlu->jlu_base_line->merge_bloom_bits(frag.bloom_bits());
923✔
2574

2575
        auto& sbc = *jlu->jlu_batch_context;
923✔
2576
        auto opid_iter = sbc.sbc_opids.los_opid_ranges.find(frag);
923✔
2577
        if (opid_iter == sbc.sbc_opids.los_opid_ranges.end()) {
923✔
2578
            jlu->jlu_opid_frag = frag.to_owned(sbc.sbc_allocator);
900✔
2579
        } else {
2580
            jlu->jlu_opid_frag = opid_iter->first;
23✔
2581
        }
2582
    }
2583
    if (!field_name.empty()
181,881✔
2584
        && jlu->jlu_format->elf_thread_id_field == field_name)
181,881✔
2585
    {
2586
        auto& sbc = *jlu->jlu_batch_context;
×
2587
        auto tid_iter = sbc.sbc_tids.ltis_tid_ranges.find(frag);
×
2588
        if (tid_iter == sbc.sbc_tids.ltis_tid_ranges.end()) {
×
2589
            jlu->jlu_tid_frag = frag.to_owned(sbc.sbc_allocator);
×
2590
        } else {
2591
            jlu->jlu_tid_frag = tid_iter->first;
×
2592
        }
2593
    }
2594
    if (!jlu->jlu_format->elf_subid_field.empty()
181,881✔
2595
        && jlu->jlu_format->elf_subid_field == field_name)
181,881✔
2596
    {
2597
        jlu->jlu_subid = frag.to_string();
×
2598
    }
2599
    if (!field_name.empty()
181,881✔
2600
        && jlu->jlu_format->elf_duration_field == field_name)
181,881✔
2601
    {
2602
        auto from_res = humanize::try_from<double>(frag);
×
2603
        if (from_res) {
×
2604
            jlu->jlu_duration = std::chrono::microseconds(
×
2605
                static_cast<int64_t>(from_res.value() * 1000000));
2606
        }
2607
    }
2608

2609
    if (vd != nullptr && vd->vd_is_desc_field) {
181,881✔
2610
        auto frag_copy = frag.to_owned(jlu->jlu_format->lf_desc_allocator);
42✔
2611

2612
        jlu->jlu_format->lf_desc_captures.emplace(field_name, frag_copy);
42✔
2613
    }
2614

2615
    jlu->add_sub_lines_for(vd, ypc->is_level(1), std::nullopt, str, len, props);
181,881✔
2616

2617
    return 1;
181,881✔
2618
}
2619

2620
static int
2621
rewrite_json_field(yajlpp_parse_context* ypc,
69,426✔
2622
                   const unsigned char* str,
2623
                   size_t len,
2624
                   yajl_string_props_t* props)
2625
{
2626
    static const intern_string_t body_name = intern_string::lookup("body", -1);
69,426✔
2627
    auto* jlu = (json_log_userdata*) ypc->ypc_userdata;
69,426✔
2628
    intern_string_t field_name;
69,426✔
2629
    const auto* vd = jlu->get_field_def(ypc);
69,426✔
2630
    auto frag = string_fragment::from_bytes(str, len);
69,426✔
2631

2632
    if (!ypc->is_level(1) && vd == nullptr) {
69,426✔
2633
        if (!jlu->jlu_format->elf_opid_field.empty()) {
57,218✔
2634
            auto path_sf = ypc->get_path_as_string_fragment();
56,598✔
2635
            if (path_sf.startswith(jlu->jlu_format->elf_opid_field.c_str())) {
56,598✔
2636
                jlu->jlu_opid_hasher.update(path_sf);
48✔
2637
                jlu->jlu_opid_hasher.update(frag);
48✔
2638
            }
2639
        }
2640
        return 1;
57,218✔
2641
    }
2642
    if (vd != nullptr) {
12,208✔
2643
        field_name = vd->vd_meta.lvm_name;
11,616✔
2644
    } else {
2645
        field_name = ypc->get_path();
592✔
2646
    }
2647

2648
    if (jlu->jlu_format->elf_opid_field == field_name) {
12,208✔
2649
        jlu->jlu_format->jlf_line_values.lvv_opid_value = frag.to_string();
956✔
2650
        jlu->jlu_format->jlf_line_values.lvv_opid_provenance
956✔
2651
            = logline_value_vector::opid_provenance::file;
956✔
2652
    }
2653
    if (jlu->jlu_format->elf_thread_id_field == field_name) {
12,208✔
2654
        jlu->jlu_format->jlf_line_values.lvv_thread_id_value = frag.to_string();
×
2655
    }
2656
    if (jlu->jlu_format->lf_timestamp_field == field_name) {
12,208✔
2657
        char time_buf[64];
2658

2659
        // TODO add a timeval kind to logline_value
2660
        if (jlu->jlu_line->is_time_skewed()
2,322✔
2661
            || jlu->jlu_line->get_msg_level() == LEVEL_INVALID
2,322✔
2662
            || (jlu->jlu_format->lf_timestamp_flags
4,644✔
2663
                & (ETF_MICROS_SET | ETF_NANOS_SET | ETF_ZONE_SET)))
2,242✔
2664
        {
2665
            timeval tv;
2666

2667
            const auto* last = jlu->jlu_format->lf_date_time.scan(
2,322✔
2668
                (const char*) str,
2669
                len,
2670
                jlu->jlu_format->get_timestamp_formats(),
2,322✔
2671
                &jlu->jlu_exttm,
2672
                tv);
2673
            if (last == nullptr) {
2,322✔
2674
                auto ls = jlu->jlu_format->lf_date_time.unlock();
124✔
2675
                if ((last = jlu->jlu_format->lf_date_time.scan(
124✔
2676
                         (const char*) str,
2677
                         len,
2678
                         jlu->jlu_format->get_timestamp_formats(),
124✔
2679
                         &jlu->jlu_exttm,
2680
                         tv))
2681
                    == nullptr)
124✔
2682
                {
2683
                    jlu->jlu_format->lf_date_time.relock(ls);
66✔
2684
                    jlu->jlu_scan_error = log_format::scan_error{
66✔
2685
                        fmt::format("failed to parse timestamp '{}' in string "
2686
                                    "property '{}'",
2687
                                    frag,
2688
                                    field_name)};
66✔
2689
                }
2690
            }
2691
            if (!jlu->jlu_subline_opts.hash_hack) {
2,322✔
2692
                if (jlu->jlu_exttm.et_flags & ETF_ZONE_SET
2,322✔
2693
                    && jlu->jlu_format->lf_date_time.dts_zoned_to_local)
2,256✔
2694
                {
2695
                    jlu->jlu_exttm.et_flags &= ~ETF_Z_IS_UTC;
2,256✔
2696
                }
2697
                jlu->jlu_exttm.et_gmtoff
2698
                    = jlu->jlu_format->lf_date_time.dts_local_offset_cache;
2,322✔
2699
            }
2700
            jlu->jlu_format->lf_date_time.ftime(
2,322✔
2701
                time_buf,
2702
                sizeof(time_buf),
2703
                jlu->jlu_format->get_timestamp_formats(),
2,322✔
2704
                jlu->jlu_exttm);
2,322✔
2705
        } else {
2706
            sql_strftime(
×
2707
                time_buf, sizeof(time_buf), jlu->jlu_line->get_timeval(), 'T');
×
2708
        }
2709
        jlu->jlu_format->jlf_line_values.lvv_values.emplace_back(
4,644✔
2710
            jlu->jlu_format->get_value_meta(field_name,
4,644✔
2711
                                            value_kind_t::VALUE_TEXT),
2712
            std::string{time_buf});
6,966✔
2713
    } else if (jlu->jlu_shared_buffer.contains((const char*) str)) {
9,886✔
2714
        auto str_offset = (int) ((const char*) str - jlu->jlu_line_value);
9,392✔
2715
        if (field_name == jlu->jlu_format->elf_body_field) {
9,392✔
2716
            jlu->jlu_format->jlf_line_values.lvv_values.emplace_back(
857✔
2717
                logline_value_meta(body_name,
1,714✔
2718
                                   value_kind_t::VALUE_TEXT,
2719
                                   logline_value_meta::internal_column{},
×
2720
                                   jlu->jlu_format),
857✔
2721
                string_fragment::from_byte_range(
1,714✔
2722
                    jlu->jlu_shared_buffer.get_data(),
857✔
2723
                    str_offset,
2724
                    str_offset + len));
857✔
2725
        }
2726

2727
        jlu->jlu_format->jlf_line_values.lvv_values.emplace_back(
9,392✔
2728
            jlu->jlu_format->get_value_meta(field_name,
18,784✔
2729
                                            value_kind_t::VALUE_TEXT),
2730
            string_fragment::from_byte_range(jlu->jlu_shared_buffer.get_data(),
18,784✔
2731
                                             str_offset,
2732
                                             str_offset + len));
9,392✔
2733
    } else {
2734
        if (field_name == jlu->jlu_format->elf_body_field) {
494✔
2735
            jlu->jlu_format->jlf_line_values.lvv_values.emplace_back(
403✔
2736
                logline_value_meta(body_name,
806✔
2737
                                   value_kind_t::VALUE_TEXT,
2738
                                   logline_value_meta::internal_column{},
×
2739
                                   jlu->jlu_format),
403✔
2740
                std::string{(const char*) str, len});
1,612✔
2741
        }
2742

2743
        jlu->jlu_format->jlf_line_values.lvv_values.emplace_back(
494✔
2744
            jlu->jlu_format->get_value_meta(ypc, vd, value_kind_t::VALUE_TEXT),
988✔
2745
            std::string{(const char*) str, len});
1,976✔
2746
    }
2747
    if (vd != nullptr && vd->vd_is_desc_field
11,616✔
2748
        && jlu->jlu_format->elf_opid_field.empty())
23,824✔
2749
    {
2750
        auto frag_copy = frag.to_owned(jlu->jlu_format->lf_desc_allocator);
48✔
2751

2752
        jlu->jlu_format->lf_desc_captures.emplace(field_name, frag_copy);
48✔
2753
    }
2754

2755
    return 1;
12,208✔
2756
}
2757

2758
void
2759
external_log_format::get_subline(const log_format_file_state& lffs,
22,160✔
2760
                                 const logline& ll,
2761
                                 shared_buffer_ref& sbr,
2762
                                 subline_options opts)
2763
{
2764
    if (this->elf_type == elf_type_t::ELF_TYPE_TEXT) {
22,160✔
2765
        return;
17,462✔
2766
    }
2767

2768
    if (this->jlf_cached_offset != ll.get_offset()
4,698✔
2769
        || this->jlf_cached_opts != opts)
4,698✔
2770
    {
2771
        auto& ypc = *(this->jlf_parse_context);
2,534✔
2772
        yajl_handle handle = this->jlf_yajl_handle.get();
2,534✔
2773
        json_log_userdata jlu(sbr, nullptr);
2,534✔
2774

2775
        jlu.jlu_subline_opts = opts;
2,534✔
2776

2777
        this->lf_desc_captures.clear();
2,534✔
2778
        this->lf_desc_allocator.reset();
2,534✔
2779
        this->jlf_share_manager.invalidate_refs();
2,534✔
2780
        this->jlf_cached_line.clear();
2,534✔
2781
        this->jlf_line_values.clear();
2,534✔
2782
        this->jlf_line_offsets.clear();
2,534✔
2783
        this->jlf_line_attrs.clear();
2,534✔
2784

2785
        auto line_frag = sbr.to_string_fragment();
2,534✔
2786

2787
        if (!line_frag.startswith("{")) {
2,534✔
2788
            this->jlf_cached_line.resize(line_frag.length());
72✔
2789
            memcpy(this->jlf_cached_line.data(),
72✔
2790
                   line_frag.data(),
72✔
2791
                   line_frag.length());
72✔
2792
            this->jlf_line_values.clear();
72✔
2793
            sbr.share(this->jlf_share_manager,
144✔
2794
                      &this->jlf_cached_line[0],
72✔
2795
                      this->jlf_cached_line.size());
2796
            this->jlf_line_values.lvv_sbr = sbr.clone();
72✔
2797
            this->jlf_line_attrs.emplace_back(
72✔
2798
                line_range{0, -1},
×
2799
                SA_INVALID.value(fmt::format(
144✔
2800
                    FMT_STRING("line at offset {} is not a JSON-line"),
144✔
2801
                    ll.get_offset())));
72✔
2802
            return;
72✔
2803
        }
2804

2805
        yajl_reset(handle);
2,462✔
2806
        ypc.set_static_handler(json_log_rewrite_handlers.jpc_children[0]);
2,462✔
2807
        ypc.ypc_userdata = &jlu;
2,462✔
2808
        ypc.ypc_ignore_unused = true;
2,462✔
2809
        ypc.ypc_alt_callbacks.yajl_start_array = json_array_start_const;
2,462✔
2810
        ypc.ypc_alt_callbacks.yajl_end_array = json_array_end;
2,462✔
2811
        ypc.ypc_alt_callbacks.yajl_start_map = json_array_start_const;
2,462✔
2812
        ypc.ypc_alt_callbacks.yajl_end_map = json_array_end;
2,462✔
2813
        jlu.jlu_format = this;
2,462✔
2814
        jlu.jlu_line = &ll;
2,462✔
2815
        jlu.jlu_handle = handle;
2,462✔
2816
        jlu.jlu_line_value = sbr.get_data();
2,462✔
2817
        jlu.jlu_format_hits.resize(this->jlf_line_format.size());
2,462✔
2818

2819
        yajl_status parse_status = yajl_parse(
4,924✔
2820
            handle, (const unsigned char*) sbr.get_data(), sbr.length());
2,462✔
2821
        if (parse_status != yajl_status_ok
2,462✔
2822
            || yajl_complete_parse(handle) != yajl_status_ok
2,462✔
2823
            || jlu.jlu_scan_error)
4,924✔
2824
        {
2825
            unsigned char* msg;
2826
            std::string full_msg;
80✔
2827

2828
            msg = yajl_get_error(
160✔
2829
                handle, 1, (const unsigned char*) sbr.get_data(), sbr.length());
80✔
2830
            if (msg != nullptr) {
80✔
2831
                full_msg = fmt::format(
80✔
2832
                    FMT_STRING("[offset: {}] {}\n{}"),
160✔
2833
                    ll.get_offset(),
80✔
2834
                    fmt::string_view{sbr.get_data(), sbr.length()},
80✔
2835
                    reinterpret_cast<char*>(msg));
160✔
2836
                yajl_free_error(handle, msg);
80✔
2837
            }
2838

2839
            this->jlf_cached_line.resize(full_msg.size());
80✔
2840
            memcpy(
80✔
2841
                this->jlf_cached_line.data(), full_msg.data(), full_msg.size());
80✔
2842
            this->jlf_line_values.clear();
80✔
2843
            this->jlf_line_attrs.emplace_back(
80✔
2844
                line_range{0, -1},
×
2845
                SA_INVALID.value(jlu.jlu_scan_error
240✔
2846
                                     ? jlu.jlu_scan_error->se_message
174✔
2847
                                     : "JSON line failed to parse"));
2848
        } else {
80✔
2849
            std::vector<logline_value>::iterator lv_iter;
2,382✔
2850
            bool used_values[this->jlf_line_values.lvv_values.size()];
4,764✔
2851
            struct line_range lr;
2,382✔
2852

2853
            memset(used_values, 0, sizeof(used_values));
2,382✔
2854
            for (lv_iter = this->jlf_line_values.lvv_values.begin();
2,382✔
2855
                 lv_iter != this->jlf_line_values.lvv_values.end();
21,064✔
2856
                 ++lv_iter)
18,682✔
2857
            {
2858
                lv_iter->lv_meta.lvm_format = this;
18,682✔
2859
            }
2860

2861
            if (jlu.jlu_tid_number) {
2,382✔
2862
                this->jlf_line_values.lvv_thread_id_value
2863
                    = fmt::to_string(jlu.jlu_tid_number.value());
140✔
2864
            } else if (jlu.jlu_tid_frag) {
2,242✔
2865
                this->jlf_line_values.lvv_thread_id_value
2866
                    = jlu.jlu_tid_frag->to_string();
×
2867
            }
2868

2869
            if (this->elf_opid_field.empty()
2,382✔
2870
                && this->lf_opid_source.value_or(
1,387✔
2871
                       opid_source_t::from_description)
1,387✔
2872
                    == opid_source_t::from_description
2873
                && this->lf_opid_description_def->size() == 1)
3,769✔
2874
            {
2875
                auto found_opid_desc = false;
215✔
2876
                const auto& od = this->lf_opid_description_def->begin()->second;
215✔
2877
                for (const auto& desc : *od.od_descriptors) {
645✔
2878
                    auto desc_iter
2879
                        = this->lf_desc_captures.find(desc.od_field.pp_value);
430✔
2880
                    if (desc_iter == this->lf_desc_captures.end()) {
430✔
2881
                        continue;
382✔
2882
                    }
2883
                    found_opid_desc = true;
48✔
2884
                    jlu.jlu_opid_hasher.update(desc_iter->second);
48✔
2885
                }
2886
                if (found_opid_desc) {
215✔
2887
                    this->jlf_line_values.lvv_opid_value
2888
                        = jlu.jlu_opid_hasher.to_string();
24✔
2889
                    this->jlf_line_values.lvv_opid_provenance
2890
                        = logline_value_vector::opid_provenance::file;
24✔
2891
                }
2892
            } else if (!jlu.jlu_opid_desc_frag && !jlu.jlu_opid_frag
4,305✔
2893
                       && jlu.jlu_duration > 0us)
4,305✔
2894
            {
2895
                jlu.jlu_opid_hasher.update(line_frag);
×
2896
                this->jlf_line_values.lvv_opid_value
2897
                    = jlu.jlu_opid_hasher.to_string();
×
2898
                this->jlf_line_values.lvv_opid_provenance
2899
                    = logline_value_vector::opid_provenance::file;
×
2900
            }
2901
            if (jlu.jlu_opid_desc_frag) {
2,382✔
2902
                this->jlf_line_values.lvv_opid_value
2903
                    = jlu.jlu_opid_hasher.to_string();
29✔
2904
                this->jlf_line_values.lvv_opid_provenance
2905
                    = logline_value_vector::opid_provenance::file;
29✔
2906
            }
2907
            if (jlu.jlu_opid_frag) {
2,382✔
2908
                this->jlf_line_values.lvv_opid_value
2909
                    = jlu.jlu_opid_frag->to_string();
×
2910
                this->jlf_line_values.lvv_opid_provenance
2911
                    = logline_value_vector::opid_provenance::file;
×
2912
            }
2913

2914
            int sub_offset = this->jlf_line_format_init_count;
2,382✔
2915
            for (const auto& jfe : this->jlf_line_format) {
30,621✔
2916
                static const intern_string_t ts_field
2917
                    = intern_string::lookup("__timestamp__", -1);
28,239✔
2918
                static const intern_string_t level_field
2919
                    = intern_string::lookup("__level__");
28,333✔
2920
                size_t begin_size = this->jlf_cached_line.size();
28,239✔
2921

2922
                switch (jfe.jfe_type) {
28,239✔
2923
                    case json_log_field::CONSTANT:
3,883✔
2924
                        this->json_append_to_cache(
3,883✔
2925
                            jfe.jfe_default_value.c_str(),
2926
                            jfe.jfe_default_value.size());
3,883✔
2927
                        break;
3,883✔
2928
                    case json_log_field::VARIABLE:
24,356✔
2929
                        lv_iter = find_if(
24,356✔
2930
                            this->jlf_line_values.lvv_values.begin(),
2931
                            this->jlf_line_values.lvv_values.end(),
2932
                            logline_value_name_cmp(&jfe.jfe_value.pp_value));
2933
                        if (lv_iter != this->jlf_line_values.lvv_values.end()) {
24,356✔
2934
                            auto str = lv_iter->to_string();
8,714✔
2935
                            value_def* vd = nullptr;
8,714✔
2936

2937
                            if (lv_iter->lv_meta.lvm_values_index) {
8,714✔
2938
                                vd = this->elf_value_def_order
2939
                                         [lv_iter->lv_meta.lvm_values_index
8,714✔
2940
                                              .value()]
8,714✔
2941
                                             .get();
8,714✔
2942
                            }
2943
                            while (endswith(str, "\n")) {
8,996✔
2944
                                str.pop_back();
282✔
2945
                            }
2946
                            size_t nl_pos = str.find('\n');
8,714✔
2947

2948
                            if (!jfe.jfe_prefix.empty()) {
8,714✔
2949
                                this->json_append_to_cache(jfe.jfe_prefix);
4,914✔
2950
                            }
2951
                            lr.lr_start = this->jlf_cached_line.size();
8,714✔
2952

2953
                            if ((int) str.size() > jfe.jfe_max_width) {
8,714✔
2954
                                switch (jfe.jfe_overflow) {
232✔
2955
                                    case json_format_element::overflow_t::
173✔
2956
                                        ABBREV: {
2957
                                        size_t new_size
2958
                                            = abbreviate_str(&str[0],
173✔
2959
                                                             str.size(),
2960
                                                             jfe.jfe_max_width);
173✔
2961
                                        str.resize(new_size);
173✔
2962
                                        this->json_append(lffs, jfe, vd, str);
173✔
2963
                                        break;
173✔
2964
                                    }
2965
                                    case json_format_element::overflow_t::
53✔
2966
                                        TRUNCATE: {
2967
                                        this->json_append_to_cache(
53✔
2968
                                            str.c_str(), jfe.jfe_max_width);
53✔
2969
                                        break;
53✔
2970
                                    }
2971
                                    case json_format_element::overflow_t::
6✔
2972
                                        DOTDOT: {
2973
                                        size_t middle
6✔
2974
                                            = (jfe.jfe_max_width / 2) - 1;
6✔
2975
                                        this->json_append_to_cache(str.c_str(),
6✔
2976
                                                                   middle);
2977
                                        this->json_append_to_cache("..", 2);
6✔
2978
                                        size_t rest
6✔
2979
                                            = (jfe.jfe_max_width - middle - 2);
6✔
2980
                                        this->json_append_to_cache(
12✔
2981
                                            str.c_str() + str.size() - rest,
6✔
2982
                                            rest);
2983
                                        break;
6✔
2984
                                    }
2985
                                    case json_format_element::overflow_t::
×
2986
                                        LASTWORD: {
2987
                                        size_t new_size
2988
                                            = last_word_str(&str[0],
×
2989
                                                            str.size(),
2990
                                                            jfe.jfe_max_width);
×
2991
                                        str.resize(new_size);
×
2992
                                        this->json_append(lffs, jfe, vd, str);
×
2993
                                        break;
×
2994
                                    }
2995
                                }
2996
                            } else {
2997
                                sub_offset
2998
                                    += std::count(str.begin(), str.end(), '\n');
8,482✔
2999
                                this->json_append(lffs, jfe, vd, str);
8,482✔
3000
                            }
3001

3002
                            if (nl_pos == std::string::npos
8,714✔
3003
                                || opts.full_message)
3✔
3004
                            {
3005
                                lr.lr_end = this->jlf_cached_line.size();
8,711✔
3006
                            } else {
3007
                                lr.lr_end = lr.lr_start + nl_pos;
3✔
3008
                            }
3009

3010
                            if (lv_iter->lv_meta.lvm_name
8,714✔
3011
                                == this->lf_timestamp_field)
8,714✔
3012
                            {
3013
                                this->jlf_line_attrs.emplace_back(
1,043✔
3014
                                    lr, L_TIMESTAMP.value());
2,086✔
3015
                            } else if (lv_iter->lv_meta.lvm_name
7,671✔
3016
                                       == this->elf_body_field)
7,671✔
3017
                            {
3018
                                this->jlf_line_attrs.emplace_back(
1,199✔
3019
                                    lr, SA_BODY.value());
2,398✔
3020
                            } else if (lv_iter->lv_meta.lvm_name
6,472✔
3021
                                       == this->elf_src_file_field)
6,472✔
3022
                            {
3023
                                this->jlf_line_attrs.emplace_back(
11✔
3024
                                    lr, SA_SRC_FILE.value());
22✔
3025
                            } else if (lv_iter->lv_meta.lvm_name
6,461✔
3026
                                       == this->elf_src_line_field)
6,461✔
3027
                            {
3028
                                this->jlf_line_attrs.emplace_back(
11✔
3029
                                    lr, SA_SRC_LINE.value());
22✔
3030
                            } else if (lv_iter->lv_meta.lvm_name
6,450✔
3031
                                       == this->elf_thread_id_field)
6,450✔
3032
                            {
3033
                                this->jlf_line_attrs.emplace_back(
89✔
3034
                                    lr, SA_THREAD_ID.value());
178✔
3035
                            } else if (lv_iter->lv_meta.lvm_name
6,361✔
3036
                                       == this->elf_duration_field)
6,361✔
3037
                            {
3038
                                this->jlf_line_attrs.emplace_back(
×
3039
                                    lr, SA_DURATION.value());
×
3040
                            } else if (lv_iter->lv_meta.lvm_name
6,361✔
3041
                                       == this->elf_level_field)
6,361✔
3042
                            {
3043
                                this->jlf_line_attrs.emplace_back(
1,105✔
3044
                                    lr, L_LEVEL.value());
2,210✔
3045
                            } else if (lv_iter->lv_meta.lvm_name
10,512✔
3046
                                           == this->elf_opid_field
5,256✔
3047
                                       && !lr.empty())
5,256✔
3048
                            {
3049
                                this->jlf_line_attrs.emplace_back(
956✔
3050
                                    lr, L_OPID.value());
1,912✔
3051
                            }
3052
                            lv_iter->lv_origin = lr;
8,714✔
3053
                            lv_iter->lv_sub_offset = sub_offset;
8,714✔
3054
                            used_values[std::distance(
8,714✔
3055
                                this->jlf_line_values.lvv_values.begin(),
3056
                                lv_iter)] = true;
8,714✔
3057

3058
                            if (!jfe.jfe_suffix.empty()) {
8,714✔
3059
                                this->json_append_to_cache(jfe.jfe_suffix);
1,321✔
3060
                            }
3061
                        } else if (jfe.jfe_value.pp_value == ts_field) {
24,356✔
3062
                            line_range lr;
1,482✔
3063
                            ssize_t ts_len;
3064
                            char ts[64];
3065
                            exttm et;
1,482✔
3066

3067
                            ll.to_exttm(et);
1,482✔
3068
                            et.et_nsec += jlu.jlu_exttm.et_nsec % 1000;
1,482✔
3069
                            et.et_gmtoff = jlu.jlu_exttm.et_gmtoff;
1,482✔
3070
                            et.et_flags |= jlu.jlu_exttm.et_flags;
1,482✔
3071
                            if (!jfe.jfe_prefix.empty()) {
1,482✔
3072
                                this->json_append_to_cache(jfe.jfe_prefix);
4✔
3073
                            }
3074
                            if (jfe.jfe_ts_format.empty()) {
1,482✔
3075
                                ts_len = this->lf_date_time.ftime(
1,327✔
3076
                                    ts,
3077
                                    sizeof(ts),
3078
                                    this->get_timestamp_formats(),
3079
                                    et);
3080
                            } else {
3081
                                ts_len = ftime_fmt(ts,
155✔
3082
                                                   sizeof(ts),
3083
                                                   jfe.jfe_ts_format.c_str(),
3084
                                                   et);
3085
                            }
3086
                            lr.lr_start = this->jlf_cached_line.size();
1,482✔
3087
                            this->json_append_to_cache(ts, ts_len);
1,482✔
3088
                            lr.lr_end = this->jlf_cached_line.size();
1,482✔
3089
                            this->jlf_line_attrs.emplace_back(
1,482✔
3090
                                lr, L_TIMESTAMP.value());
2,964✔
3091

3092
                            lv_iter = find_if(
1,482✔
3093
                                this->jlf_line_values.lvv_values.begin(),
3094
                                this->jlf_line_values.lvv_values.end(),
3095
                                logline_value_name_cmp(
3096
                                    &this->lf_timestamp_field));
1,482✔
3097
                            if (lv_iter
1,482✔
3098
                                != this->jlf_line_values.lvv_values.end())
1,482✔
3099
                            {
3100
                                used_values[distance(
1,482✔
3101
                                    this->jlf_line_values.lvv_values.begin(),
3102
                                    lv_iter)] = true;
1,482✔
3103
                            }
3104
                            if (!jfe.jfe_suffix.empty()) {
1,482✔
3105
                                this->json_append_to_cache(jfe.jfe_suffix);
4✔
3106
                            }
3107
                        } else if (jfe.jfe_value.pp_value == level_field
14,160✔
3108
                                   || jfe.jfe_value.pp_value
28,200✔
3109
                                       == this->elf_level_field)
14,040✔
3110
                        {
3111
                            auto level_name = ll.get_level_name();
120✔
3112
                            lr.lr_start = this->jlf_cached_line.size();
120✔
3113
                            this->json_append(lffs, jfe, nullptr, level_name);
120✔
3114
                            if (jfe.jfe_auto_width) {
120✔
3115
                                this->json_append_to_cache(
89✔
3116
                                    MAX_LEVEL_NAME_LEN - level_name.length());
89✔
3117
                            }
3118
                            lr.lr_end = this->jlf_cached_line.size();
120✔
3119
                            this->jlf_line_attrs.emplace_back(lr,
120✔
3120
                                                              L_LEVEL.value());
240✔
3121
                        } else if (!jfe.jfe_default_value.empty()) {
14,040✔
3122
                            if (!jfe.jfe_prefix.empty()) {
122✔
3123
                                this->json_append_to_cache(jfe.jfe_prefix);
×
3124
                            }
3125
                            this->json_append(
122✔
3126
                                lffs, jfe, nullptr, jfe.jfe_default_value);
122✔
3127
                            if (!jfe.jfe_suffix.empty()) {
122✔
3128
                                this->json_append_to_cache(jfe.jfe_suffix);
×
3129
                            }
3130
                        }
3131

3132
                        switch (jfe.jfe_text_transform) {
24,356✔
3133
                            case json_format_element::transform_t::NONE:
24,236✔
3134
                                break;
24,236✔
3135
                            case json_format_element::transform_t::UPPERCASE:
120✔
3136
                                for (size_t cindex = begin_size;
120✔
3137
                                     cindex < this->jlf_cached_line.size();
960✔
3138
                                     cindex++)
3139
                                {
3140
                                    this->jlf_cached_line[cindex] = toupper(
840✔
3141
                                        this->jlf_cached_line[cindex]);
840✔
3142
                                }
3143
                                break;
120✔
3144
                            case json_format_element::transform_t::LOWERCASE:
×
3145
                                for (size_t cindex = begin_size;
×
3146
                                     cindex < this->jlf_cached_line.size();
×
3147
                                     cindex++)
3148
                                {
3149
                                    this->jlf_cached_line[cindex] = tolower(
×
3150
                                        this->jlf_cached_line[cindex]);
×
3151
                                }
3152
                                break;
×
3153
                            case json_format_element::transform_t::CAPITALIZE:
×
3154
                                for (size_t cindex = begin_size;
×
3155
                                     cindex < begin_size + 1;
×
3156
                                     cindex++)
3157
                                {
3158
                                    this->jlf_cached_line[cindex] = toupper(
×
3159
                                        this->jlf_cached_line[cindex]);
×
3160
                                }
3161
                                for (size_t cindex = begin_size + 1;
×
3162
                                     cindex < this->jlf_cached_line.size();
×
3163
                                     cindex++)
3164
                                {
3165
                                    this->jlf_cached_line[cindex] = tolower(
×
3166
                                        this->jlf_cached_line[cindex]);
×
3167
                                }
3168
                                break;
×
3169
                        }
3170
                        break;
24,356✔
3171
                }
3172
            }
3173
            this->json_append_to_cache("\n", 1);
2,382✔
3174
            sub_offset += 1;
2,382✔
3175

3176
            for (size_t lpc = 0; lpc < this->jlf_line_values.lvv_values.size();
21,064✔
3177
                 lpc++)
3178
            {
3179
                static const intern_string_t body_name
3180
                    = intern_string::lookup("body", -1);
18,682✔
3181
                auto& lv = this->jlf_line_values.lvv_values[lpc];
18,682✔
3182

3183
                if (lv.lv_meta.is_hidden() || used_values[lpc]
28,706✔
3184
                    || body_name == lv.lv_meta.lvm_name)
28,706✔
3185
                {
3186
                    continue;
17,518✔
3187
                }
3188

3189
                auto str = lv.to_string();
1,164✔
3190
                while (endswith(str, "\n")) {
1,164✔
3191
                    str.pop_back();
×
3192
                }
3193

3194
                lv.lv_sub_offset = sub_offset;
1,164✔
3195
                lv.lv_origin.lr_start = this->jlf_cached_line.size() + 2
1,164✔
3196
                    + lv.lv_meta.lvm_name.size() + 2;
1,164✔
3197
                auto frag = string_fragment::from_str(str);
1,164✔
3198
                while (true) {
3199
                    auto utf_scan_res = is_utf8(frag, '\n');
1,170✔
3200

3201
                    this->json_append_to_cache("  ", 2);
1,170✔
3202
                    this->json_append_to_cache(
1,170✔
3203
                        lv.lv_meta.lvm_name.to_string_fragment());
1,170✔
3204
                    this->json_append_to_cache(": ", 2);
1,170✔
3205
                    lr.lr_start = this->jlf_cached_line.size();
1,170✔
3206
                    this->json_append_to_cache(utf_scan_res.usr_valid_frag);
1,170✔
3207
                    lr.lr_end = this->jlf_cached_line.size();
1,170✔
3208
                    if (lv.lv_meta.lvm_name == this->elf_body_field) {
1,170✔
3209
                        this->jlf_line_attrs.emplace_back(lr, SA_BODY.value());
×
3210
                    } else {
3211
                        this->jlf_line_attrs.emplace_back(
1,170✔
3212
                            lr, SA_EXTRA_CONTENT.value());
2,340✔
3213
                    }
3214
                    this->json_append_to_cache("\n", 1);
1,170✔
3215
                    sub_offset += 1;
1,170✔
3216
                    if (utf_scan_res.usr_remaining) {
1,170✔
3217
                        frag = utf_scan_res.usr_remaining.value();
6✔
3218
                    } else {
3219
                        break;
1,164✔
3220
                    }
3221
                }
6✔
3222
                lv.lv_origin.lr_end = this->jlf_cached_line.size() - 1;
1,164✔
3223
                if (lv.lv_meta.lvm_name == this->elf_opid_field
1,164✔
3224
                    && !lv.lv_origin.empty())
1,164✔
3225
                {
3226
                    this->jlf_line_attrs.emplace_back(lv.lv_origin,
×
3227
                                                      L_OPID.value());
×
3228
                }
3229
            }
1,164✔
3230
        }
2,382✔
3231

3232
        this->jlf_line_offsets.push_back(0);
2,462✔
3233
        for (size_t lpc = 0; lpc < this->jlf_cached_line.size(); lpc++) {
282,056✔
3234
            if (this->jlf_cached_line[lpc] == '\n') {
279,594✔
3235
                this->jlf_line_offsets.push_back(lpc + 1);
4,651✔
3236
            }
3237
        }
3238
        this->jlf_line_offsets.push_back(this->jlf_cached_line.size());
2,462✔
3239
        this->jlf_cached_offset = ll.get_offset();
2,462✔
3240
        this->jlf_cached_opts = opts;
2,462✔
3241
    }
2,534✔
3242

3243
    off_t this_off = 0, next_off = 0;
4,626✔
3244

3245
    if (!this->jlf_line_offsets.empty()
4,626✔
3246
        && ll.get_sub_offset() < this->jlf_line_offsets.size())
4,626✔
3247
    {
3248
        require(ll.get_sub_offset() < this->jlf_line_offsets.size());
4,626✔
3249

3250
        this_off = this->jlf_line_offsets[ll.get_sub_offset()];
4,626✔
3251
        if ((ll.get_sub_offset() + 1) < (int) this->jlf_line_offsets.size()) {
4,626✔
3252
            next_off = this->jlf_line_offsets[ll.get_sub_offset() + 1];
4,626✔
3253
        } else {
3254
            next_off = this->jlf_cached_line.size();
×
3255
        }
3256
        if (next_off > 0 && this->jlf_cached_line[next_off - 1] == '\n'
4,626✔
3257
            && this_off != next_off)
9,252✔
3258
        {
3259
            next_off -= 1;
4,626✔
3260
        }
3261
    }
3262

3263
    if (opts.full_message) {
4,626✔
3264
        sbr.share(this->jlf_share_manager,
642✔
3265
                  &this->jlf_cached_line[0],
321✔
3266
                  this->jlf_cached_line.size());
3267
    } else {
3268
        sbr.share(this->jlf_share_manager,
8,610✔
3269
                  this->jlf_cached_line.data() + this_off,
4,305✔
3270
                  next_off - this_off);
4,305✔
3271
    }
3272
    sbr.get_metadata().m_valid_utf = ll.is_valid_utf();
4,626✔
3273
    sbr.get_metadata().m_has_ansi = ll.has_ansi();
4,626✔
3274
    this->jlf_cached_sub_range.lr_start = this_off;
4,626✔
3275
    this->jlf_cached_sub_range.lr_end = next_off;
4,626✔
3276
    this->jlf_line_values.lvv_sbr = sbr.clone();
4,626✔
3277
}
3278

3279
struct compiled_header_expr {
3280
    auto_mem<sqlite3_stmt> che_stmt{sqlite3_finalize};
3281
    bool che_enabled{true};
3282
};
3283

3284
struct format_header_expressions : public lnav_config_listener {
3285
    format_header_expressions() : lnav_config_listener(__FILE__) {}
1,178✔
3286

3287
    auto_sqlite3 e_db;
3288
    std::map<intern_string_t, std::map<std::string, compiled_header_expr>>
3289
        e_header_exprs;
3290
};
3291

3292
using safe_format_header_expressions = safe::Safe<format_header_expressions>;
3293

3294
static safe_format_header_expressions format_header_exprs;
3295

3296
std::optional<external_file_format>
3297
detect_mime_type(const std::filesystem::path& filename)
621✔
3298
{
3299
    uint8_t buffer[1024];
3300
    size_t buffer_size = 0;
621✔
3301

3302
    {
3303
        auto_fd fd;
621✔
3304

3305
        if ((fd = lnav::filesystem::openp(filename, O_RDONLY)) == -1) {
621✔
3306
            return std::nullopt;
×
3307
        }
3308

3309
        ssize_t rc;
3310

3311
        if ((rc = read(fd, buffer, sizeof(buffer))) == -1) {
621✔
3312
            return std::nullopt;
×
3313
        }
3314
        buffer_size = rc;
621✔
3315
    }
621✔
3316

3317
    auto hexbuf = auto_buffer::alloc(buffer_size * 2);
621✔
3318

3319
    for (size_t lpc = 0; lpc < buffer_size; lpc++) {
328,839✔
3320
        fmt::format_to(
328,218✔
3321
            std::back_inserter(hexbuf), FMT_STRING("{:02x}"), buffer[lpc]);
1,312,872✔
3322
    }
3323

3324
    safe::WriteAccess<safe_format_header_expressions> in(format_header_exprs);
621✔
3325

3326
    for (const auto& format : log_format::get_root_formats()) {
47,081✔
3327
        auto elf = std::dynamic_pointer_cast<external_log_format>(format);
46,460✔
3328
        if (elf == nullptr) {
46,460✔
3329
            continue;
3,105✔
3330
        }
3331

3332
        if (elf->elf_converter.c_header.h_exprs.he_exprs.empty()) {
43,355✔
3333
            continue;
42,113✔
3334
        }
3335

3336
        if (buffer_size < elf->elf_converter.c_header.h_size) {
1,242✔
3337
            log_debug(
107✔
3338
                "%s: file content too small (%zu) for header detection: %s",
3339
                filename.c_str(),
3340
                buffer_size,
3341
                elf->get_name().get());
3342
            continue;
107✔
3343
        }
3344
        for (const auto& hpair : elf->elf_converter.c_header.h_exprs.he_exprs) {
4,070✔
3345
            auto& he = in->e_header_exprs[elf->get_name()][hpair.first];
2,935✔
3346

3347
            if (!he.che_enabled) {
2,935✔
3348
                continue;
×
3349
            }
3350

3351
            auto* stmt = he.che_stmt.in();
2,935✔
3352

3353
            if (stmt == nullptr) {
2,935✔
3354
                continue;
×
3355
            }
3356
            sqlite3_reset(stmt);
2,935✔
3357
            auto count = sqlite3_bind_parameter_count(stmt);
2,935✔
3358
            for (int lpc = 0; lpc < count; lpc++) {
5,870✔
3359
                const auto* name = sqlite3_bind_parameter_name(stmt, lpc + 1);
2,935✔
3360

3361
                if (name[0] == '$') {
2,935✔
3362
                    const char* env_value;
3363

3364
                    if ((env_value = getenv(&name[1])) != nullptr) {
×
3365
                        sqlite3_bind_text(
×
3366
                            stmt, lpc + 1, env_value, -1, SQLITE_STATIC);
3367
                    }
3368
                    continue;
×
3369
                }
3370
                if (strcmp(name, ":header") == 0) {
2,935✔
3371
                    sqlite3_bind_text(stmt,
2,935✔
3372
                                      lpc + 1,
3373
                                      hexbuf.in(),
2,935✔
3374
                                      hexbuf.size(),
2,935✔
3375
                                      SQLITE_STATIC);
3376
                    continue;
2,935✔
3377
                }
3378
                if (strcmp(name, ":filepath") == 0) {
×
3379
                    sqlite3_bind_text(
×
3380
                        stmt, lpc + 1, filename.c_str(), -1, SQLITE_STATIC);
3381
                    continue;
×
3382
                }
3383
            }
3384

3385
            auto step_res = sqlite3_step(stmt);
2,935✔
3386

3387
            switch (step_res) {
2,935✔
3388
                case SQLITE_OK:
2,935✔
3389
                case SQLITE_DONE:
3390
                    continue;
2,935✔
3391
                case SQLITE_ROW:
×
3392
                    break;
×
3393
                default: {
×
3394
                    log_error(
×
3395
                        "failed to execute file-format header expression: "
3396
                        "%s:%s -- %s",
3397
                        elf->get_name().get(),
3398
                        hpair.first.c_str(),
3399
                        sqlite3_errmsg(in->e_db));
3400
                    he.che_enabled = false;
×
3401
                    continue;
×
3402
                }
3403
            }
3404

3405
            log_info("detected format for: %s -- %s (header-expr: %s)",
×
3406
                     filename.c_str(),
3407
                     elf->get_name().get(),
3408
                     hpair.first.c_str());
3409
            return external_file_format{
×
3410
                elf->get_name().to_string(),
×
3411
                elf->elf_converter.c_command.pp_value,
×
3412
                elf->elf_converter.c_command.pp_location.sl_source.to_string(),
×
3413
            };
3414
        }
3415
    }
46,460✔
3416

3417
    return std::nullopt;
621✔
3418
}
621✔
3419

3420
log_format::scan_result_t
3421
log_format::test_line(sample_t& sample,
×
3422
                      std::vector<lnav::console::user_message>& msgs)
3423
{
3424
    return scan_no_match{};
×
3425
}
3426

3427
log_format::scan_result_t
3428
external_log_format::test_line(sample_t& sample,
188,008✔
3429
                               std::vector<lnav::console::user_message>& msgs)
3430
{
3431
    auto lines
3432
        = string_fragment::from_str(sample.s_line.pp_value).split_lines();
188,008✔
3433

3434
    if (this->elf_type == elf_type_t::ELF_TYPE_JSON) {
188,008✔
3435
        auto alloc = ArenaAlloc::Alloc<char>{};
3,034✔
3436
        pattern_locks pats;
3,034✔
3437
        auto sbc = scan_batch_context{
3,034✔
3438
            alloc,
3439
            pats,
3440
        };
3,034✔
3441
        sbc.sbc_value_stats.resize(this->elf_value_defs.size());
3,034✔
3442
        std::vector<logline> dst;
3,034✔
3443
        auto li = line_info{
3,034✔
3444
            {0, lines[0].length()},
3,034✔
3445
        };
3,034✔
3446
        shared_buffer sb;
3,034✔
3447
        shared_buffer_ref sbr;
3,034✔
3448
        sbr.share(sb, lines[0].data(), (size_t) lines[0].length());
3,034✔
3449

3450
        return this->scan_json(dst, li, sbr, sbc);
3,034✔
3451
    }
3,034✔
3452

3453
    scan_result_t retval = scan_no_match{"no patterns matched"};
184,974✔
3454
    auto found = false;
184,974✔
3455

3456
    for (auto pat_iter = this->elf_pattern_order.begin();
184,974✔
3457
         pat_iter != this->elf_pattern_order.end();
1,391,942✔
3458
         ++pat_iter)
1,206,968✔
3459
    {
3460
        auto& pat = *(*pat_iter);
1,206,968✔
3461

3462
        if (!pat.p_pcre.pp_value) {
1,206,968✔
3463
            continue;
1,021,996✔
3464
        }
3465

3466
        auto md = pat.p_pcre.pp_value->create_match_data();
1,206,968✔
3467
        auto match_res = pat.p_pcre.pp_value->capture_from(lines[0])
1,206,968✔
3468
                             .into(md)
1,206,968✔
3469
                             .matches(PCRE2_NO_UTF_CHECK)
2,413,936✔
3470
                             .ignore_error();
1,206,968✔
3471
        if (!match_res) {
1,206,968✔
3472
            continue;
1,021,996✔
3473
        }
3474
        retval = scan_match{1000};
184,972✔
3475
        found = true;
184,972✔
3476

3477
        sample.s_matched_regexes.insert(pat.p_name.to_string());
184,972✔
3478

3479
        const auto ts_cap = md[pat.p_timestamp_field_index];
184,972✔
3480
        const auto level_cap = md[pat.p_level_field_index];
184,972✔
3481
        const char* const* custom_formats = this->get_timestamp_formats();
184,972✔
3482
        date_time_scanner dts;
184,972✔
3483
        timeval tv;
3484
        exttm tm;
184,972✔
3485

3486
        if (ts_cap && ts_cap->sf_begin == 0) {
184,972✔
3487
            pat.p_timestamp_end = ts_cap->sf_end;
113,331✔
3488
        }
3489
        const char* dts_scan_res = nullptr;
184,972✔
3490

3491
        if (ts_cap) {
184,972✔
3492
            dts_scan_res = dts.scan(
184,970✔
3493
                ts_cap->data(), ts_cap->length(), custom_formats, &tm, tv);
184,970✔
3494
        }
3495
        if (dts_scan_res != nullptr) {
184,972✔
3496
            if (dts_scan_res != ts_cap->data() + ts_cap->length()) {
184,969✔
3497
                auto match_len = dts_scan_res - ts_cap->data();
×
3498
                auto notes = attr_line_t("the used timestamp format: ");
×
3499
                if (custom_formats == nullptr) {
×
3500
                    notes.append(PTIMEC_FORMATS[dts.dts_fmt_lock].pf_fmt);
×
3501
                } else {
3502
                    notes.append(custom_formats[dts.dts_fmt_lock]);
×
3503
                }
3504
                notes.append("\n  ")
×
3505
                    .append(ts_cap.value())
×
3506
                    .append("\n")
×
3507
                    .append(2 + match_len, ' ')
×
3508
                    .append("^ matched up to here"_snippet_border);
×
3509
                auto um = lnav::console::user_message::warning(
×
3510
                              attr_line_t("timestamp was not fully matched: ")
×
3511
                                  .append_quoted(ts_cap.value()))
×
3512
                              .with_snippet(sample.s_line.to_snippet())
×
3513
                              .with_note(notes)
×
3514
                              .move();
×
3515

3516
                msgs.emplace_back(um);
×
3517
            }
3518
        } else if (!ts_cap) {
3✔
3519
            msgs.emplace_back(
2✔
3520
                lnav::console::user_message::error(
×
3521
                    attr_line_t("invalid sample log message: ")
4✔
3522
                        .append(lnav::to_json(sample.s_line.pp_value)))
4✔
3523
                    .with_reason(attr_line_t("timestamp was not captured"))
4✔
3524
                    .with_snippet(sample.s_line.to_snippet())
4✔
3525
                    .with_help(attr_line_t(
4✔
3526
                        "A timestamp needs to be captured in order for a "
3527
                        "line to be recognized as a log message")));
3528
        } else {
3529
            attr_line_t notes;
1✔
3530

3531
            if (custom_formats == nullptr) {
1✔
3532
                notes.append("the following built-in formats were tried:");
×
3533
                for (int lpc = 0; PTIMEC_FORMATS[lpc].pf_fmt != nullptr; lpc++)
×
3534
                {
3535
                    off_t off = 0;
×
3536

3537
                    PTIMEC_FORMATS[lpc].pf_func(
×
3538
                        &tm, ts_cap->data(), off, ts_cap->length());
×
3539
                    notes.append("\n  ")
×
3540
                        .append(ts_cap.value())
×
3541
                        .append("\n")
×
3542
                        .append(2 + off, ' ')
×
3543
                        .append("^ "_snippet_border)
×
3544
                        .append_quoted(
×
3545
                            lnav::roles::symbol(PTIMEC_FORMATS[lpc].pf_fmt))
×
3546
                        .append(" matched up to here"_snippet_border);
×
3547
                }
3548
            } else {
3549
                notes.append("the following custom formats were tried:");
1✔
3550
                for (int lpc = 0; custom_formats[lpc] != nullptr; lpc++) {
2✔
3551
                    off_t off = 0;
1✔
3552

3553
                    ptime_fmt(custom_formats[lpc],
1✔
3554
                              &tm,
3555
                              ts_cap->data(),
3556
                              off,
3557
                              ts_cap->length());
1✔
3558
                    notes.append("\n  ")
1✔
3559
                        .append(ts_cap.value())
1✔
3560
                        .append("\n")
1✔
3561
                        .append(2 + off, ' ')
1✔
3562
                        .append("^ "_snippet_border)
1✔
3563
                        .append_quoted(lnav::roles::symbol(custom_formats[lpc]))
2✔
3564
                        .append(" matched up to here"_snippet_border);
1✔
3565
                }
3566
            }
3567

3568
            msgs.emplace_back(
1✔
3569
                lnav::console::user_message::error(
×
3570
                    attr_line_t("invalid sample log message: ")
1✔
3571
                        .append(lnav::to_json(sample.s_line.pp_value)))
2✔
3572
                    .with_reason(attr_line_t("unrecognized timestamp -- ")
2✔
3573
                                     .append(ts_cap.value()))
1✔
3574
                    .with_snippet(sample.s_line.to_snippet())
2✔
3575
                    .with_note(notes)
1✔
3576
                    .with_help(attr_line_t("If the timestamp format is not "
2✔
3577
                                           "supported by default, you can "
3578
                                           "add a custom format with the ")
3579
                                   .append_quoted("timestamp-format"_symbol)
1✔
3580
                                   .append(" property")));
1✔
3581
        }
1✔
3582

3583
        auto level = this->convert_level(
184,972✔
3584
            level_cap.value_or(string_fragment::invalid()), nullptr);
184,972✔
3585

3586
        if (sample.s_level != LEVEL_UNKNOWN && sample.s_level != level) {
184,972✔
3587
            attr_line_t note_al;
1✔
3588

3589
            note_al.append("matched regex = ")
1✔
3590
                .append(lnav::roles::symbol(pat.p_name.to_string()))
2✔
3591
                .append("\n")
1✔
3592
                .append("captured level = ")
1✔
3593
                .append_quoted(level_cap->to_string());
1✔
3594
            if (level_cap && !this->elf_level_patterns.empty()) {
1✔
3595
                thread_local auto md = lnav::pcre2pp::match_data::unitialized();
1✔
3596

3597
                note_al.append("\nlevel regular expression match results:");
1✔
3598
                for (const auto& level_pattern : this->elf_level_patterns) {
3✔
3599
                    attr_line_t regex_al
3600
                        = level_pattern.second.lp_pcre.pp_value->get_pattern();
2✔
3601
                    lnav::snippets::regex_highlighter(
2✔
3602
                        regex_al, -1, line_range{0, (int) regex_al.length()});
2✔
3603
                    note_al.append("\n  ")
2✔
3604
                        .append(lnav::roles::symbol(
4✔
3605
                            level_pattern.second.lp_pcre.pp_path.to_string()))
4✔
3606
                        .append(" = ")
2✔
3607
                        .append(regex_al)
2✔
3608
                        .append("\n    ");
2✔
3609
                    auto match_res = level_pattern.second.lp_pcre.pp_value
2✔
3610
                                         ->capture_from(level_cap.value())
2✔
3611
                                         .into(md)
2✔
3612
                                         .matches(PCRE2_NO_UTF_CHECK)
4✔
3613
                                         .ignore_error();
2✔
3614
                    if (!match_res) {
2✔
3615
                        note_al.append(lnav::roles::warning("no match"));
1✔
3616
                        continue;
1✔
3617
                    }
3618

3619
                    note_al.append(level_cap.value())
1✔
3620
                        .append("\n    ")
1✔
3621
                        .append(md.leading().length(), ' ')
1✔
3622
                        .append("^"_snippet_border);
1✔
3623
                    if (match_res->f_all.length() > 2) {
1✔
3624
                        note_al.append(lnav::roles::snippet_border(
1✔
3625
                            std::string(match_res->f_all.length() - 2, '-')));
3✔
3626
                    }
3627
                    if (match_res->f_all.length() > 1) {
1✔
3628
                        note_al.append("^"_snippet_border);
1✔
3629
                    }
3630
                }
2✔
3631
            }
3632
            auto um
3633
                = lnav::console::user_message::error(
×
3634
                      attr_line_t("invalid sample log message: ")
1✔
3635
                          .append(lnav::to_json(sample.s_line.pp_value)))
2✔
3636
                      .with_reason(attr_line_t()
2✔
3637
                                       .append_quoted(lnav::roles::symbol(
2✔
3638
                                           level_names[level]))
1✔
3639
                                       .append(" does not match the expected "
1✔
3640
                                               "level of ")
3641
                                       .append_quoted(lnav::roles::symbol(
2✔
3642
                                           level_names[sample.s_level])))
1✔
3643
                      .with_snippet(sample.s_line.to_snippet())
2✔
3644
                      .with_note(note_al)
1✔
3645
                      .move();
1✔
3646
            if (!this->elf_level_patterns.empty()) {
1✔
3647
                um.with_help(
1✔
3648
                    attr_line_t("Level regexes are not anchored to the "
2✔
3649
                                "start/end of the string.  Prepend ")
3650
                        .append_quoted("^"_symbol)
1✔
3651
                        .append(" to the expression to match from the "
1✔
3652
                                "start of the string and append ")
3653
                        .append_quoted("$"_symbol)
1✔
3654
                        .append(" to match up to the end of the string."));
1✔
3655
            }
3656
            msgs.emplace_back(um);
1✔
3657
        }
1✔
3658

3659
        {
3660
            auto full_match_res
3661
                = pat.p_pcre.pp_value->capture_from(sample.s_line.pp_value)
184,972✔
3662
                      .into(md)
184,972✔
3663
                      .matches()
369,944✔
3664
                      .ignore_error();
184,972✔
3665
            if (!full_match_res) {
184,972✔
3666
                attr_line_t regex_al = pat.p_pcre.pp_value->get_pattern();
1✔
3667
                lnav::snippets::regex_highlighter(
1✔
3668
                    regex_al, -1, line_range{0, (int) regex_al.length()});
1✔
3669
                msgs.emplace_back(
1✔
3670
                    lnav::console::user_message::error(
×
3671
                        attr_line_t("invalid pattern: ")
1✔
3672
                            .append_quoted(
1✔
3673
                                lnav::roles::symbol(pat.p_name.to_string())))
2✔
3674
                        .with_reason("pattern does not match entire "
2✔
3675
                                     "multiline sample message")
3676
                        .with_snippet(sample.s_line.to_snippet())
2✔
3677
                        .with_note(attr_line_t()
2✔
3678
                                       .append(lnav::roles::symbol(
1✔
3679
                                           pat.p_name.to_string()))
2✔
3680
                                       .append(" = ")
1✔
3681
                                       .append(regex_al))
1✔
3682
                        .with_help(
3683
                            attr_line_t("use ").append_quoted(".*").append(
2✔
3684
                                " to match new-lines")));
3685
            } else if (static_cast<size_t>(full_match_res->f_all.length())
184,972✔
3686
                       != sample.s_line.pp_value.length())
184,971✔
3687
            {
3688
                attr_line_t regex_al = pat.p_pcre.pp_value->get_pattern();
1✔
3689
                lnav::snippets::regex_highlighter(
1✔
3690
                    regex_al, -1, line_range{0, (int) regex_al.length()});
1✔
3691
                auto match_length
3692
                    = static_cast<size_t>(full_match_res->f_all.length());
1✔
3693
                attr_line_t sample_al = sample.s_line.pp_value;
1✔
3694
                sample_al.append("\n")
1✔
3695
                    .append(match_length, ' ')
1✔
3696
                    .append("^ matched up to here"_error)
1✔
3697
                    .with_attr_for_all(VC_ROLE.value(role_t::VCR_QUOTED_CODE));
1✔
3698
                auto sample_snippet = lnav::console::snippet::from(
3699
                    sample.s_line.pp_location, sample_al);
1✔
3700
                msgs.emplace_back(
1✔
3701
                    lnav::console::user_message::error(
×
3702
                        attr_line_t("invalid pattern: ")
1✔
3703
                            .append_quoted(
1✔
3704
                                lnav::roles::symbol(pat.p_name.to_string())))
2✔
3705
                        .with_reason("pattern does not match entire "
2✔
3706
                                     "message")
3707
                        .with_snippet(sample_snippet)
1✔
3708
                        .with_note(attr_line_t()
3✔
3709
                                       .append(lnav::roles::symbol(
2✔
3710
                                           pat.p_name.to_string()))
2✔
3711
                                       .append(" = ")
1✔
3712
                                       .append(regex_al))
1✔
3713
                        .with_help("update the regular expression to fully "
3714
                                   "capture the sample message"));
3715
            }
1✔
3716
        }
3717
    }
1,206,968✔
3718

3719
    if (!found && !this->elf_pattern_order.empty()) {
184,974✔
3720
        std::vector<std::pair<ssize_t, intern_string_t>> partial_indexes;
2✔
3721
        attr_line_t notes;
2✔
3722
        size_t max_name_width = 0;
2✔
3723

3724
        for (const auto& pat_iter : this->elf_pattern_order) {
10✔
3725
            auto& pat = *pat_iter;
8✔
3726

3727
            if (!pat.p_pcre.pp_value) {
8✔
3728
                continue;
×
3729
            }
3730

3731
            partial_indexes.emplace_back(
8✔
3732
                pat.p_pcre.pp_value->match_partial(lines[0]), pat.p_name);
8✔
3733
            max_name_width = std::max(max_name_width, pat.p_name.size());
8✔
3734
        }
3735
        for (const auto& line_frag : lines) {
4✔
3736
            auto src_line = attr_line_t(line_frag.to_string());
2✔
3737
            if (!line_frag.endswith("\n")) {
2✔
3738
                src_line.append("\n");
2✔
3739
            }
3740
            src_line.with_attr_for_all(VC_ROLE.value(role_t::VCR_QUOTED_CODE));
2✔
3741
            notes.append("   ").append(src_line);
2✔
3742
            for (auto& part_pair : partial_indexes) {
10✔
3743
                if (part_pair.first >= 0
16✔
3744
                    && part_pair.first < line_frag.length())
8✔
3745
                {
3746
                    notes.append("   ")
8✔
3747
                        .append(part_pair.first, ' ')
8✔
3748
                        .append("^ "_snippet_border)
8✔
3749
                        .append(
8✔
3750
                            lnav::roles::symbol(part_pair.second.to_string()))
16✔
3751
                        .append(" matched up to here"_snippet_border)
8✔
3752
                        .append("\n");
8✔
3753
                }
3754
                part_pair.first -= line_frag.length();
8✔
3755
            }
3756
        }
2✔
3757
        notes.add_header(
2✔
3758
            "the following shows how each pattern matched this sample:\n");
3759

3760
        attr_line_t regex_note;
2✔
3761
        for (const auto& pat_iter : this->elf_pattern_order) {
10✔
3762
            if (!pat_iter->p_pcre.pp_value) {
8✔
3763
                regex_note
3764
                    .append(lnav::roles::symbol(fmt::format(
×
3765
                        FMT_STRING("{:{}}"), pat_iter->p_name, max_name_width)))
×
3766
                    .append(" is invalid");
×
3767
                continue;
×
3768
            }
3769

3770
            attr_line_t regex_al = pat_iter->p_pcre.pp_value->get_pattern();
8✔
3771
            lnav::snippets::regex_highlighter(
8✔
3772
                regex_al, -1, line_range{0, (int) regex_al.length()});
8✔
3773

3774
            regex_note
3775
                .append(lnav::roles::symbol(fmt::format(
16✔
3776
                    FMT_STRING("{:{}}"), pat_iter->p_name, max_name_width)))
24✔
3777
                .append(" = ")
8✔
3778
                .append_quoted(regex_al)
16✔
3779
                .append("\n");
8✔
3780
        }
8✔
3781

3782
        msgs.emplace_back(
2✔
3783
            lnav::console::user_message::error(
×
3784
                attr_line_t("invalid sample log message: ")
2✔
3785
                    .append(lnav::to_json(sample.s_line.pp_value)))
4✔
3786
                .with_reason("sample does not match any patterns")
4✔
3787
                .with_snippet(sample.s_line.to_snippet())
4✔
3788
                .with_note(notes.rtrim())
4✔
3789
                .with_note(regex_note));
3790
    }
2✔
3791

3792
    return retval;
184,974✔
3793
}
188,008✔
3794

3795
void
3796
external_log_format::build(std::vector<lnav::console::user_message>& errors)
53,368✔
3797
{
3798
    auto& vc = view_colors::singleton();
53,368✔
3799

3800
    if (!this->lf_timestamp_field.empty()) {
53,368✔
3801
        auto& vd = this->elf_value_defs[this->lf_timestamp_field];
53,368✔
3802
        if (vd.get() == nullptr) {
53,368✔
3803
            vd = std::make_shared<value_def>(
38,870✔
3804
                this->lf_timestamp_field,
38,870✔
3805
                value_kind_t::VALUE_TEXT,
×
3806
                logline_value_meta::internal_column{},
×
3807
                this);
38,870✔
3808
            if (this->elf_type == elf_type_t::ELF_TYPE_JSON) {
38,870✔
3809
                this->elf_value_def_order.emplace_back(vd);
6,546✔
3810
            }
3811
        }
3812
        vd->vd_meta.lvm_name = this->lf_timestamp_field;
53,368✔
3813
        vd->vd_meta.lvm_kind = value_kind_t::VALUE_TEXT;
53,368✔
3814
        vd->vd_meta.lvm_column = logline_value_meta::internal_column{};
53,368✔
3815
        vd->vd_internal = true;
53,368✔
3816

3817
        this->elf_value_defs[LOG_TIME_STR] = vd;
53,368✔
3818
    }
3819

3820
    if (!this->lf_subsecond_field.empty()) {
53,368✔
3821
        if (!this->lf_subsecond_unit.has_value()) {
97✔
3822
            errors.emplace_back(
1✔
3823
                lnav::console::user_message::error(
×
3824
                    attr_line_t()
2✔
3825
                        .append_quoted(
1✔
3826
                            lnav::roles::symbol(this->elf_name.to_string()))
2✔
3827
                        .append(" is not a valid log format"))
1✔
3828
                    .with_reason(attr_line_t()
2✔
3829
                                     .append_quoted("subsecond-units"_symbol)
1✔
3830
                                     .append(" must be set when ")
1✔
3831
                                     .append_quoted("subsecond-field"_symbol)
1✔
3832
                                     .append(" is used"))
1✔
3833
                    .with_snippets(this->get_snippets()));
2✔
3834
        } else {
3835
            auto& vd = this->elf_value_defs[this->lf_subsecond_field];
96✔
3836
            if (vd.get() == nullptr) {
96✔
3837
                vd = std::make_shared<value_def>(
96✔
3838
                    this->lf_subsecond_field,
96✔
3839
                    value_kind_t::VALUE_INTEGER,
×
3840
                    logline_value_meta::internal_column{},
×
3841
                    this);
96✔
3842
                if (this->elf_type == elf_type_t::ELF_TYPE_JSON) {
96✔
3843
                    this->elf_value_def_order.emplace_back(vd);
96✔
3844
                }
3845
            }
3846
            vd->vd_meta.lvm_name = this->lf_subsecond_field;
96✔
3847
            vd->vd_meta.lvm_column = logline_value_meta::internal_column{};
96✔
3848
            vd->vd_meta.lvm_hidden = true;
96✔
3849
            vd->vd_internal = true;
96✔
3850
        }
3851
    }
3852

3853
    if (startswith(this->elf_level_field.get(), "/")) {
53,368✔
3854
        this->elf_level_field
3855
            = intern_string::lookup(this->elf_level_field.get() + 1);
192✔
3856
    }
3857
    if (!this->elf_level_field.empty()) {
53,368✔
3858
        auto level_iter = this->elf_value_defs.find(this->elf_level_field);
53,368✔
3859
        if (level_iter == this->elf_value_defs.end()) {
53,368✔
3860
            auto& vd = this->elf_value_defs[this->elf_level_field];
25,887✔
3861
            vd = std::make_shared<value_def>(
25,887✔
3862
                this->elf_level_field,
25,887✔
3863
                value_kind_t::VALUE_TEXT,
×
3864
                logline_value_meta::internal_column{},
×
3865
                this);
25,887✔
3866
            if (this->elf_type == elf_type_t::ELF_TYPE_JSON) {
25,887✔
3867
                this->elf_value_def_order.emplace_back(vd);
2,660✔
3868
            }
3869
            vd->vd_meta.lvm_name = this->elf_level_field;
25,887✔
3870
            vd->vd_meta.lvm_column = logline_value_meta::internal_column{};
25,887✔
3871
            vd->vd_internal = true;
25,887✔
3872

3873
            if (this->elf_level_field != this->elf_body_field) {
25,887✔
3874
                this->elf_value_defs[LOG_LEVEL_STR] = vd;
25,033✔
3875
            }
3876
        } else {
3877
            if (level_iter->second->vd_meta.lvm_kind
27,481✔
3878
                != value_kind_t::VALUE_TEXT)
27,481✔
3879
            {
3880
                this->lf_level_hideable = false;
5,402✔
3881
            }
3882
            this->elf_value_defs[LOG_LEVEL_STR] = level_iter->second;
27,481✔
3883
        }
3884
    }
3885

3886
    auto opid_field_iter = this->elf_value_defs.find(LOG_OPID_STR);
53,368✔
3887
    if (opid_field_iter == this->elf_value_defs.end()) {
53,368✔
3888
        auto vd
3889
            = std::make_shared<value_def>(this->elf_opid_field,
53,368✔
3890
                                          value_kind_t::VALUE_TEXT,
×
3891
                                          logline_value_meta::internal_column{},
×
3892
                                          this);
53,368✔
3893
        if (this->elf_type == elf_type_t::ELF_TYPE_JSON) {
53,368✔
3894
            this->elf_value_def_order.emplace_back(vd);
10,432✔
3895
        }
3896
        vd->vd_meta.lvm_name = LOG_OPID_STR;
53,368✔
3897
        vd->vd_meta.lvm_kind = value_kind_t::VALUE_TEXT;
53,368✔
3898
        vd->vd_meta.lvm_column = logline_value_meta::internal_column{};
53,368✔
3899
        vd->vd_internal = true;
53,368✔
3900

3901
        this->elf_value_defs[LOG_OPID_STR] = vd;
53,368✔
3902
    }
53,368✔
3903

3904
    if (!this->elf_body_field.empty()) {
53,368✔
3905
        auto& vd = this->elf_value_defs[this->elf_body_field];
53,368✔
3906
        if (vd.get() == nullptr) {
53,368✔
3907
            vd = std::make_shared<value_def>(
43,321✔
3908
                this->elf_body_field,
43,321✔
3909
                value_kind_t::VALUE_TEXT,
×
3910
                logline_value_meta::internal_column{},
×
3911
                this);
43,321✔
3912
            if (this->elf_type == elf_type_t::ELF_TYPE_JSON) {
43,321✔
3913
                this->elf_value_def_order.emplace_back(vd);
6,545✔
3914
            }
3915
        }
3916
        vd->vd_meta.lvm_name = this->elf_body_field;
53,368✔
3917
        vd->vd_meta.lvm_kind = value_kind_t::VALUE_TEXT;
53,368✔
3918
        vd->vd_meta.lvm_column = logline_value_meta::internal_column{};
53,368✔
3919
        vd->vd_internal = true;
53,368✔
3920
    }
3921

3922
    if (!this->elf_src_file_field.empty()) {
53,368✔
3923
        auto& vd = this->elf_value_defs[this->elf_src_file_field];
7,581✔
3924
        if (vd.get() == nullptr) {
7,581✔
3925
            vd = std::make_shared<value_def>(
1✔
3926
                this->elf_src_file_field,
1✔
3927
                value_kind_t::VALUE_TEXT,
×
3928
                logline_value_meta::internal_column{},
×
3929
                this);
2✔
3930
        }
3931
        vd->vd_meta.lvm_name = this->elf_src_file_field;
7,581✔
3932
        vd->vd_meta.lvm_kind = value_kind_t::VALUE_TEXT;
7,581✔
3933
        vd->vd_meta.lvm_column = logline_value_meta::internal_column{};
7,581✔
3934
    }
3935

3936
    if (!this->elf_src_line_field.empty()) {
53,368✔
3937
        auto& vd = this->elf_value_defs[this->elf_src_line_field];
7,581✔
3938
        if (vd.get() == nullptr) {
7,581✔
3939
            vd = std::make_shared<value_def>(
1✔
3940
                this->elf_src_line_field,
1✔
3941
                value_kind_t::VALUE_INTEGER,
×
3942
                logline_value_meta::internal_column{},
×
3943
                this);
2✔
3944
        }
3945
        vd->vd_meta.lvm_name = this->elf_src_line_field;
7,581✔
3946
        vd->vd_meta.lvm_kind = value_kind_t::VALUE_INTEGER;
7,581✔
3947
        vd->vd_meta.lvm_column = logline_value_meta::internal_column{};
7,581✔
3948
    }
3949

3950
    if (!this->elf_thread_id_field.empty()) {
53,368✔
3951
        auto& vd = this->elf_value_defs[this->elf_thread_id_field];
17,435✔
3952
        if (vd.get() == nullptr) {
17,435✔
3953
            vd = std::make_shared<value_def>(
1✔
3954
                this->elf_thread_id_field,
1✔
3955
                value_kind_t::VALUE_TEXT,
×
3956
                logline_value_meta::internal_column{},
×
3957
                this);
2✔
3958
        }
3959
        vd->vd_meta.lvm_name = this->elf_thread_id_field;
17,435✔
3960
        vd->vd_meta.lvm_kind = value_kind_t::VALUE_TEXT;
17,435✔
3961
        vd->vd_meta.lvm_column = logline_value_meta::internal_column{};
17,435✔
3962
    }
3963

3964
    if (!this->elf_duration_field.empty()) {
53,368✔
3965
        auto& vd = this->elf_value_defs[this->elf_duration_field];
2,274✔
3966
        if (vd.get() == nullptr) {
2,274✔
3967
            vd = std::make_shared<value_def>(
×
3968
                this->elf_duration_field,
×
3969
                value_kind_t::VALUE_FLOAT,
×
3970
                logline_value_meta::internal_column{},
×
3971
                this);
×
3972
        }
3973
        vd->vd_meta.lvm_name = this->elf_duration_field;
2,274✔
3974
        vd->vd_meta.lvm_kind = value_kind_t::VALUE_FLOAT;
2,274✔
3975
        vd->vd_meta.lvm_column = logline_value_meta::internal_column{};
2,274✔
3976
    }
3977

3978
    for (auto& od_pair : *this->lf_opid_description_def) {
76,110✔
3979
        od_pair.second.od_index = this->lf_opid_description_def_vec->size();
22,742✔
3980
        this->lf_opid_description_def_vec->emplace_back(&od_pair.second);
22,742✔
3981
    }
3982

3983
    for (auto& od_pair : *this->lf_subid_description_def) {
54,126✔
3984
        od_pair.second.od_index = this->lf_subid_description_def_vec->size();
758✔
3985
        this->lf_subid_description_def_vec->emplace_back(&od_pair.second);
758✔
3986
    }
3987

3988
    if (!this->lf_timestamp_format.empty()) {
53,368✔
3989
        this->lf_timestamp_format.push_back(nullptr);
6,164✔
3990
    }
3991
    auto src_file_found = 0;
53,368✔
3992
    auto src_line_found = 0;
53,368✔
3993
    auto thread_id_found = 0;
53,368✔
3994
    auto duration_found = 0;
53,368✔
3995
    for (auto& elf_pattern : this->elf_patterns) {
151,738✔
3996
        auto& pat = *elf_pattern.second;
98,370✔
3997

3998
        if (pat.p_pcre.pp_value == nullptr) {
98,370✔
3999
            continue;
1✔
4000
        }
4001

4002
        if (pat.p_opid_field_index == -1
196,738✔
4003
            && this->lf_opid_source.value_or(opid_source_t::from_description)
98,369✔
4004
                == opid_source_t::from_description
4005
            && this->lf_opid_description_def->size() == 1)
196,738✔
4006
        {
4007
            const auto& opid_def
4008
                = this->lf_opid_description_def->begin()->second;
25,014✔
4009
            for (const auto& desc : *opid_def.od_descriptors) {
56,092✔
4010
                for (auto named_cap : pat.p_pcre.pp_value->get_named_captures())
362,324✔
4011
                {
4012
                    const intern_string_t name
4013
                        = intern_string::lookup(named_cap.get_name());
331,246✔
4014

4015
                    if (name == desc.od_field.pp_value) {
331,246✔
4016
                        pat.p_opid_description_field_indexes.emplace_back(
57,608✔
4017
                            named_cap.get_index());
28,804✔
4018
                    }
4019
                }
4020
            }
4021
        }
4022

4023
        for (auto named_cap : pat.p_pcre.pp_value->get_named_captures()) {
849,974✔
4024
            const intern_string_t name
4025
                = intern_string::lookup(named_cap.get_name());
751,605✔
4026

4027
            if (name == this->lf_timestamp_field) {
751,605✔
4028
                pat.p_timestamp_field_index = named_cap.get_index();
98,367✔
4029
            }
4030
            if (name == this->lf_time_field) {
751,605✔
4031
                pat.p_time_field_index = named_cap.get_index();
758✔
4032
            }
4033
            if (name == this->elf_level_field) {
751,605✔
4034
                pat.p_level_field_index = named_cap.get_index();
74,670✔
4035
            }
4036
            if (name == this->elf_opid_field) {
751,605✔
4037
                pat.p_opid_field_index = named_cap.get_index();
20,466✔
4038
            }
4039
            if (name == this->elf_subid_field) {
751,605✔
4040
                pat.p_subid_field_index = named_cap.get_index();
11,370✔
4041
            }
4042
            if (name == this->elf_body_field) {
751,605✔
4043
                pat.p_body_field_index = named_cap.get_index();
84,723✔
4044
            }
4045
            if (name == this->elf_src_file_field) {
751,605✔
4046
                pat.p_src_file_field_index = named_cap.get_index();
12,128✔
4047
                src_file_found += 1;
12,128✔
4048
            }
4049
            if (name == this->elf_src_line_field) {
751,605✔
4050
                pat.p_src_line_field_index = named_cap.get_index();
13,644✔
4051
                src_line_found += 1;
13,644✔
4052
            }
4053
            if (name == this->elf_thread_id_field) {
751,605✔
4054
                pat.p_thread_id_field_index = named_cap.get_index();
42,448✔
4055
                thread_id_found += 1;
42,448✔
4056
            }
4057
            if (name == this->elf_duration_field) {
751,605✔
4058
                pat.p_duration_field_index = named_cap.get_index();
4,548✔
4059
                duration_found += 1;
4,548✔
4060
            }
4061

4062
            auto value_iter = this->elf_value_defs.find(name);
751,605✔
4063
            if (value_iter != this->elf_value_defs.end()) {
751,605✔
4064
                auto vd = value_iter->second;
740,137✔
4065
                indexed_value_def ivd;
740,137✔
4066

4067
                ivd.ivd_index = named_cap.get_index();
740,137✔
4068
                if (!vd->vd_unit_field.empty()) {
740,137✔
4069
                    ivd.ivd_unit_field_index = pat.p_pcre.pp_value->name_index(
1,516✔
4070
                        vd->vd_unit_field.get());
758✔
4071
                } else {
4072
                    ivd.ivd_unit_field_index = -1;
739,379✔
4073
                }
4074
                if (!vd->vd_internal
740,137✔
4075
                    && !vd->vd_meta.lvm_column
1,272,639✔
4076
                            .is<logline_value_meta::table_column>())
532,502✔
4077
                {
4078
                    vd->vd_meta.lvm_column = logline_value_meta::table_column{
298,280✔
4079
                        this->elf_column_count++};
298,280✔
4080
                }
4081
                ivd.ivd_value_def = vd;
740,137✔
4082
                pat.p_value_by_index.push_back(ivd);
740,137✔
4083
            }
740,137✔
4084
            pat.p_value_name_to_index[name] = named_cap.get_index();
751,605✔
4085
        }
4086

4087
        stable_sort(pat.p_value_by_index.begin(), pat.p_value_by_index.end());
98,369✔
4088

4089
        for (int lpc = 0; lpc < (int) pat.p_value_by_index.size(); lpc++) {
838,506✔
4090
            auto& ivd = pat.p_value_by_index[lpc];
740,137✔
4091
            auto vd = ivd.ivd_value_def;
740,137✔
4092

4093
            if (!vd->vd_meta.lvm_foreign_key && !vd->vd_meta.lvm_identifier) {
740,137✔
4094
                switch (vd->vd_meta.lvm_kind) {
375,346✔
4095
                    case value_kind_t::VALUE_INTEGER:
52,398✔
4096
                    case value_kind_t::VALUE_FLOAT:
4097
                        pat.p_numeric_value_indexes.push_back(lpc);
52,398✔
4098
                        break;
52,398✔
4099
                    default:
322,948✔
4100
                        break;
322,948✔
4101
                }
4102
            }
4103
        }
740,137✔
4104

4105
        if (pat.p_timestamp_field_index == -1) {
98,369✔
4106
            errors.emplace_back(
2✔
4107
                lnav::console::user_message::error(
×
4108
                    attr_line_t("invalid pattern: ")
4✔
4109
                        .append_quoted(lnav::roles::symbol(pat.p_config_path)))
4✔
4110
                    .with_reason("no timestamp capture found in the pattern")
4✔
4111
                    .with_snippets(this->get_snippets())
4✔
4112
                    .with_help("all log messages need a timestamp"));
4113
        }
4114

4115
        if (!this->elf_level_field.empty() && pat.p_level_field_index == -1) {
98,369✔
4116
            log_warning("%s:level field '%s' not found in pattern",
23,699✔
4117
                        pat.p_config_path.c_str(),
4118
                        this->elf_level_field.get());
4119
        }
4120
        if (!this->elf_body_field.empty() && pat.p_body_field_index == -1) {
98,369✔
4121
            log_warning("%s:body field '%s' not found in pattern",
13,646✔
4122
                        pat.p_config_path.c_str(),
4123
                        this->elf_body_field.get());
4124
        }
4125

4126
        this->elf_pattern_order.push_back(elf_pattern.second);
98,369✔
4127
    }
4128
    if (this->elf_type == elf_type_t::ELF_TYPE_TEXT
106,736✔
4129
        && !this->elf_src_file_field.empty() && src_file_found == 0)
53,368✔
4130
    {
4131
        errors.emplace_back(
1✔
4132
            lnav::console::user_message::error(
×
4133
                attr_line_t("invalid pattern: ")
2✔
4134
                    .append_quoted(
1✔
4135
                        lnav::roles::symbol(this->elf_name.to_string())))
2✔
4136
                .with_reason("no source file capture found in the pattern")
2✔
4137
                .with_snippets(this->get_snippets())
2✔
4138
                .with_help(attr_line_t("at least one pattern needs a source "
2✔
4139
                                       "file capture named ")
4140
                               .append_quoted(this->elf_src_file_field.get())));
1✔
4141
    }
4142
    if (this->elf_type == elf_type_t::ELF_TYPE_TEXT
106,736✔
4143
        && !this->elf_src_line_field.empty() && src_line_found == 0)
53,368✔
4144
    {
4145
        errors.emplace_back(
1✔
4146
            lnav::console::user_message::error(
×
4147
                attr_line_t("invalid pattern: ")
2✔
4148
                    .append_quoted(
1✔
4149
                        lnav::roles::symbol(this->elf_name.to_string())))
2✔
4150
                .with_reason("no source line capture found in the pattern")
2✔
4151
                .with_snippets(this->get_snippets())
2✔
4152
                .with_help(attr_line_t("at least one pattern needs a source "
2✔
4153
                                       "line capture named ")
4154
                               .append_quoted(this->elf_src_line_field.get())));
1✔
4155
    }
4156
    if (this->elf_type == elf_type_t::ELF_TYPE_TEXT
106,736✔
4157
        && !this->elf_thread_id_field.empty() && thread_id_found == 0)
53,368✔
4158
    {
4159
        errors.emplace_back(
1✔
4160
            lnav::console::user_message::error(
×
4161
                attr_line_t("invalid pattern: ")
2✔
4162
                    .append_quoted(
1✔
4163
                        lnav::roles::symbol(this->elf_name.to_string())))
2✔
4164
                .with_reason("no thread ID capture found in the pattern")
2✔
4165
                .with_snippets(this->get_snippets())
2✔
4166
                .with_help(
4167
                    attr_line_t(
2✔
4168
                        "at least one pattern needs a thread ID capture named ")
4169
                        .append_quoted(this->elf_thread_id_field.get())));
1✔
4170
    }
4171
    if (this->elf_type == elf_type_t::ELF_TYPE_TEXT
106,736✔
4172
        && !this->elf_duration_field.empty() && duration_found == 0)
53,368✔
4173
    {
4174
        errors.emplace_back(
×
4175
            lnav::console::user_message::error(
×
4176
                attr_line_t("invalid pattern: ")
×
4177
                    .append_quoted(
×
4178
                        lnav::roles::symbol(this->elf_name.to_string())))
×
4179
                .with_reason("no duration capture found in the pattern")
×
4180
                .with_snippets(this->get_snippets())
×
4181
                .with_help(
4182
                    attr_line_t(
×
4183
                        "at least one pattern needs a duration capture named ")
4184
                        .append_quoted(this->elf_duration_field.get())));
×
4185
    }
4186

4187
    if (this->elf_type != elf_type_t::ELF_TYPE_TEXT) {
53,368✔
4188
        if (!this->elf_patterns.empty()) {
10,432✔
4189
            errors.emplace_back(
1✔
4190
                lnav::console::user_message::error(
×
4191
                    attr_line_t()
2✔
4192
                        .append_quoted(
1✔
4193
                            lnav::roles::symbol(this->elf_name.to_string()))
2✔
4194
                        .append(" is not a valid log format"))
1✔
4195
                    .with_reason("structured logs cannot have regexes")
2✔
4196
                    .with_snippets(this->get_snippets()));
2✔
4197
        }
4198
        if (this->elf_type == elf_type_t::ELF_TYPE_JSON) {
10,432✔
4199
            this->lf_multiline = true;
10,432✔
4200
            this->lf_structured = true;
10,432✔
4201
            this->lf_formatted_lines = true;
10,432✔
4202
            this->jlf_parse_context
4203
                = std::make_shared<yajlpp_parse_context>(this->elf_name);
10,432✔
4204
            this->jlf_yajl_handle.reset(
10,432✔
4205
                yajl_alloc(&this->jlf_parse_context->ypc_callbacks,
10,432✔
4206
                           nullptr,
4207
                           this->jlf_parse_context.get()),
10,432✔
4208
                yajl_handle_deleter());
4209
            yajl_config(
10,432✔
4210
                this->jlf_yajl_handle.get(), yajl_dont_validate_strings, 1);
4211
        }
4212
    } else {
4213
        if (this->elf_patterns.empty()) {
42,936✔
4214
            errors.emplace_back(lnav::console::user_message::error(
2✔
4215
                                    attr_line_t()
4✔
4216
                                        .append_quoted(lnav::roles::symbol(
4✔
4217
                                            this->elf_name.to_string()))
4✔
4218
                                        .append(" is not a valid log format"))
2✔
4219
                                    .with_reason("no regexes specified")
4✔
4220
                                    .with_snippets(this->get_snippets()));
4✔
4221
        }
4222
    }
4223

4224
    stable_sort(this->elf_level_pairs.begin(), this->elf_level_pairs.end());
53,368✔
4225

4226
    {
4227
        safe::WriteAccess<safe_format_header_expressions> hexprs(
4228
            format_header_exprs);
53,368✔
4229

4230
        if (hexprs->e_db.in() == nullptr) {
53,368✔
4231
            if (sqlite3_open(":memory:", hexprs->e_db.out()) != SQLITE_OK) {
758✔
4232
                log_error("unable to open memory DB");
×
4233
                return;
×
4234
            }
4235
            register_sqlite_funcs(hexprs->e_db.in(), sqlite_registration_funcs);
758✔
4236
        }
4237

4238
        for (const auto& hpair : this->elf_converter.c_header.h_exprs.he_exprs)
57,159✔
4239
        {
4240
            auto stmt_str
4241
                = fmt::format(FMT_STRING("SELECT 1 WHERE {}"), hpair.second);
11,373✔
4242
            compiled_header_expr che;
3,791✔
4243

4244
            log_info("preparing file-format header expression: %s",
3,791✔
4245
                     stmt_str.c_str());
4246
            auto retcode = sqlite3_prepare_v2(hexprs->e_db.in(),
7,582✔
4247
                                              stmt_str.c_str(),
4248
                                              stmt_str.size(),
3,791✔
4249
                                              che.che_stmt.out(),
4250
                                              nullptr);
4251
            if (retcode != SQLITE_OK) {
3,791✔
4252
                auto sql_al = attr_line_t(hpair.second)
2✔
4253
                                  .with_attr_for_all(SA_PREFORMATTED.value())
2✔
4254
                                  .with_attr_for_all(
1✔
4255
                                      VC_ROLE.value(role_t::VCR_QUOTED_CODE))
2✔
4256
                                  .move();
1✔
4257
                readline_sql_highlighter(
1✔
4258
                    sql_al, lnav::sql::dialect::sqlite, std::nullopt);
4259
                intern_string_t watch_expr_path = intern_string::lookup(
4260
                    fmt::format(FMT_STRING("/{}/converter/header/expr/{}"),
3✔
4261
                                this->elf_name,
1✔
4262
                                hpair.first));
2✔
4263
                auto snippet = lnav::console::snippet::from(
4264
                    source_location(watch_expr_path), sql_al);
1✔
4265

4266
                auto um = lnav::console::user_message::error(
2✔
4267
                              "SQL expression is invalid")
4268
                              .with_reason(sqlite3_errmsg(hexprs->e_db.in()))
2✔
4269
                              .with_snippet(snippet)
1✔
4270
                              .move();
1✔
4271

4272
                errors.emplace_back(um);
1✔
4273
                continue;
1✔
4274
            }
1✔
4275

4276
            hexprs->e_header_exprs[this->elf_name][hpair.first]
3,790✔
4277
                = std::move(che);
7,580✔
4278
        }
3,792✔
4279

4280
        if (!this->elf_converter.c_header.h_exprs.he_exprs.empty()
53,368✔
4281
            && this->elf_converter.c_command.pp_value.empty())
53,368✔
4282
        {
4283
            auto um = lnav::console::user_message::error(
2✔
4284
                          "A command is required when a converter is defined")
4285
                          .with_help(
2✔
4286
                              "The converter command transforms the file "
4287
                              "into a format that can be consumed by lnav")
4288
                          .with_snippets(this->get_snippets())
2✔
4289
                          .move();
1✔
4290
            errors.emplace_back(um);
1✔
4291
        }
1✔
4292
    }
53,368✔
4293

4294
    for (auto& vd : this->elf_value_def_order) {
528,141✔
4295
        std::vector<std::string>::iterator act_iter;
474,773✔
4296

4297
        if (!vd->vd_internal
474,773✔
4298
            && log_vtab_impl::RESERVED_COLUMNS.count(
899,576✔
4299
                vd->vd_meta.lvm_name.to_string_fragment()))
899,576✔
4300
        {
4301
            auto um = lnav::console::user_message::error(
×
4302
                          attr_line_t("value name ")
1✔
4303
                              .append_quoted(lnav::roles::symbol(
2✔
4304
                                  fmt::format(FMT_STRING("/{}/value/{}"),
4✔
4305
                                              this->elf_name,
1✔
4306
                                              vd->vd_meta.lvm_name)))
1✔
4307
                              .append(" is reserved and cannot be used"))
1✔
4308
                          .with_reason(
2✔
4309
                              "lnav automatically defines several columns in "
4310
                              "the log virtual table")
4311
                          .with_snippets(this->get_snippets())
2✔
4312
                          .with_help("Choose another name")
2✔
4313
                          .move();
1✔
4314
            errors.emplace_back(um);
1✔
4315
        }
1✔
4316

4317
        vd->vd_meta.lvm_format = this;
474,773✔
4318
        if (!vd->vd_internal
474,773✔
4319
            && !vd->vd_meta.lvm_column.is<logline_value_meta::table_column>())
474,773✔
4320
        {
4321
            vd->vd_meta.lvm_column
126,523✔
4322
                = logline_value_meta::table_column{this->elf_column_count++};
126,523✔
4323
        }
4324

4325
        if (vd->vd_meta.lvm_kind == value_kind_t::VALUE_UNKNOWN) {
474,773✔
4326
            vd->vd_meta.lvm_kind = value_kind_t::VALUE_TEXT;
×
4327
        }
4328

4329
        if (this->elf_type == elf_type_t::ELF_TYPE_TEXT) {
474,773✔
4330
            std::set<std::string> available_captures;
314,200✔
4331

4332
            bool found_in_pattern = false;
314,200✔
4333
            for (const auto& pat : this->elf_patterns) {
530,234✔
4334
                if (pat.second->p_pcre.pp_value == nullptr) {
530,232✔
4335
                    continue;
×
4336
                }
4337

4338
                auto cap_index = pat.second->p_pcre.pp_value->name_index(
1,060,464✔
4339
                    vd->vd_meta.lvm_name.get());
530,232✔
4340
                if (cap_index >= 0) {
530,232✔
4341
                    found_in_pattern = true;
314,198✔
4342
                    break;
314,198✔
4343
                }
4344

4345
                for (auto named_cap :
216,034✔
4346
                     pat.second->p_pcre.pp_value->get_named_captures())
1,971,576✔
4347
                {
4348
                    available_captures.insert(named_cap.get_name().to_string());
1,539,508✔
4349
                }
4350
            }
4351
            if (!found_in_pattern) {
314,200✔
4352
                auto notes
4353
                    = attr_line_t("the following captures are available:\n  ")
2✔
4354
                          .join(available_captures,
2✔
4355
                                VC_ROLE.value(role_t::VCR_SYMBOL),
4✔
4356
                                ", ")
4357
                          .move();
2✔
4358
                errors.emplace_back(
2✔
4359
                    lnav::console::user_message::warning(
×
4360
                        attr_line_t("invalid value ")
2✔
4361
                            .append_quoted(lnav::roles::symbol(
4✔
4362
                                fmt::format(FMT_STRING("/{}/value/{}"),
8✔
4363
                                            this->elf_name,
2✔
4364
                                            vd->vd_meta.lvm_name.get()))))
4✔
4365
                        .with_reason(
4✔
4366
                            attr_line_t("no patterns have a capture named ")
4✔
4367
                                .append_quoted(vd->vd_meta.lvm_name.get()))
2✔
4368
                        .with_note(notes)
2✔
4369
                        .with_snippets(this->get_snippets())
4✔
4370
                        .with_help("values are populated from captures in "
4371
                                   "patterns, so at least one pattern must "
4372
                                   "have a capture with this value name"));
4373
            }
2✔
4374
        }
314,200✔
4375

4376
        for (act_iter = vd->vd_action_list.begin();
474,773✔
4377
             act_iter != vd->vd_action_list.end();
475,531✔
4378
             ++act_iter)
758✔
4379
        {
4380
            if (this->lf_action_defs.find(*act_iter)
758✔
4381
                == this->lf_action_defs.end())
1,516✔
4382
            {
4383
#if 0
4384
                errors.push_back("error:" + this->elf_name.to_string() + ":"
4385
                                 + vd->vd_meta.lvm_name.get()
4386
                                 + ": cannot find action -- " + (*act_iter));
4387
#endif
4388
            }
4389
        }
4390

4391
        vd->set_rewrite_src_name();
474,773✔
4392

4393
        for (auto& hd_pair : vd->vd_highlighter_patterns) {
474,869✔
4394
            auto& hd = hd_pair.second;
96✔
4395
            text_attrs attrs;
96✔
4396

4397
            if (!hd.hd_color.pp_value.empty()) {
96✔
4398
                attrs.ta_fg_color = vc.match_color(
96✔
4399
                    styling::color_unit::from_str(hd.hd_color.pp_value)
192✔
NEW
4400
                        .unwrapOrElse([&](const auto& msg) {
×
NEW
4401
                            errors.emplace_back(
×
4402
                                lnav::console::user_message::error(
NEW
4403
                                    attr_line_t()
×
NEW
4404
                                        .append_quoted(hd.hd_color.pp_value)
×
NEW
4405
                                        .append(
×
4406
                                            " is not a valid color value for "
4407
                                            "property ")
NEW
4408
                                        .append_quoted(lnav::roles::symbol(
×
NEW
4409
                                            hd.hd_color.pp_path.to_string())))
×
NEW
4410
                                    .with_reason(msg)
×
NEW
4411
                                    .with_snippet(hd.hd_color.to_snippet()));
×
NEW
4412
                            return styling::color_unit::EMPTY;
×
4413
                        }));
4414
            }
4415

4416
            if (!hd.hd_background_color.pp_value.empty()) {
96✔
NEW
4417
                attrs.ta_bg_color = vc.match_color(
×
NEW
4418
                    styling::color_unit::from_str(
×
NEW
4419
                        hd.hd_background_color.pp_value)
×
NEW
4420
                        .unwrapOrElse([&](const auto& msg) {
×
NEW
4421
                            errors.emplace_back(
×
4422
                                lnav::console::user_message::error(
NEW
4423
                                    attr_line_t()
×
NEW
4424
                                        .append_quoted(
×
NEW
4425
                                            hd.hd_background_color.pp_value)
×
NEW
4426
                                        .append(
×
4427
                                            " is not a valid color value for "
4428
                                            "property ")
NEW
4429
                                        .append_quoted(lnav::roles::symbol(
×
NEW
4430
                                            hd.hd_background_color.pp_path
×
4431
                                                .to_string())))
NEW
4432
                                    .with_reason(msg)
×
4433
                                    .with_snippet(
NEW
4434
                                        hd.hd_background_color.to_snippet()));
×
NEW
4435
                            return styling::color_unit::EMPTY;
×
4436
                        }));
4437
            }
4438

4439
            if (hd.hd_underline) {
96✔
NEW
4440
                attrs |= text_attrs::style::underline;
×
4441
            }
4442
            if (hd.hd_blink) {
96✔
NEW
4443
                attrs |= text_attrs::style::blink;
×
4444
            }
4445

4446
            if (hd.hd_pattern.pp_value != nullptr) {
96✔
4447
                this->lf_highlighters.emplace_back(hd.hd_pattern.pp_value);
96✔
4448
                this->lf_highlighters.back()
96✔
4449
                    .with_field(vd->vd_meta.lvm_name)
96✔
4450
                    .with_name(hd_pair.first.to_string())
192✔
4451
                    .with_attrs(attrs)
96✔
4452
                    .with_nestable(hd.hd_nestable);
96✔
4453
            }
4454
        }
4455
    }
4456

4457
    if (this->elf_type == elf_type_t::ELF_TYPE_JSON) {
53,368✔
4458
        for (const auto& vd : this->elf_value_def_order) {
171,005✔
4459
            this->elf_value_def_frag_map[vd->vd_meta.lvm_name
160,573✔
4460
                                             .to_string_fragment()] = vd.get();
160,573✔
4461
        }
4462
    }
4463

4464
    for (const auto& td_pair : this->lf_tag_defs) {
55,741✔
4465
        const auto& td = td_pair.second;
2,373✔
4466

4467
        if (td->ftd_pattern.pp_value == nullptr
2,373✔
4468
            || td->ftd_pattern.pp_value->get_pattern().empty())
2,373✔
4469
        {
4470
            errors.emplace_back(
3✔
4471
                lnav::console::user_message::error(
×
4472
                    attr_line_t("invalid tag definition ")
6✔
4473
                        .append_quoted(lnav::roles::symbol(
6✔
4474
                            fmt::format(FMT_STRING("/{}/tags/{}"),
12✔
4475
                                        this->elf_name,
3✔
4476
                                        td_pair.first))))
3✔
4477
                    .with_reason(
6✔
4478
                        "tag definitions must have a non-empty pattern")
4479
                    .with_snippets(this->get_snippets()));
6✔
4480
        }
4481
    }
4482

4483
    if (this->elf_opid_field.empty()
53,368✔
4484
        && this->lf_opid_description_def->size() > 1)
53,368✔
4485
    {
4486
        errors.emplace_back(
1✔
4487
            lnav::console::user_message::error(
×
4488
                attr_line_t("too many opid descriptions")
2✔
4489
                    .append_quoted(lnav::roles::symbol(fmt::format(
2✔
4490
                        FMT_STRING("/{}/opid/description"), this->elf_name))))
4✔
4491
                .with_reason(attr_line_t("when no ")
2✔
4492
                                 .append("opid-field"_symbol)
1✔
4493
                                 .append(" is specified, only a single "
1✔
4494
                                         "description is supported"))
4495
                .with_snippets(this->get_snippets()));
2✔
4496
    }
4497

4498
    for (const auto& opid_desc_pair : *this->lf_opid_description_def) {
76,110✔
4499
        for (const auto& opid_desc : *opid_desc_pair.second.od_descriptors) {
53,822✔
4500
            auto iter = this->elf_value_defs.find(opid_desc.od_field.pp_value);
31,080✔
4501
            if (iter == this->elf_value_defs.end()) {
31,080✔
4502
                errors.emplace_back(
2✔
4503
                    lnav::console::user_message::error(
×
4504
                        attr_line_t("invalid opid description field ")
4✔
4505
                            .append_quoted(lnav::roles::symbol(
4✔
4506
                                opid_desc.od_field.pp_path.to_string())))
4✔
4507
                        .with_reason(
4✔
4508
                            attr_line_t("unknown value name ")
4✔
4509
                                .append_quoted(opid_desc.od_field.pp_value))
2✔
4510
                        .with_snippets(this->get_snippets()));
4✔
4511
            } else {
4512
                this->lf_desc_fields.insert(iter->first);
31,078✔
4513
                iter->second->vd_is_desc_field = true;
31,078✔
4514
            }
4515
        }
4516
    }
4517

4518
    for (const auto& subid_desc_pair : *this->lf_subid_description_def) {
54,126✔
4519
        for (const auto& subid_desc : *subid_desc_pair.second.od_descriptors) {
1,516✔
4520
            auto iter = this->elf_value_defs.find(subid_desc.od_field.pp_value);
758✔
4521
            if (iter == this->elf_value_defs.end()) {
758✔
4522
                errors.emplace_back(
×
4523
                    lnav::console::user_message::error(
×
4524
                        attr_line_t("invalid subid description field ")
×
4525
                            .append_quoted(lnav::roles::symbol(
×
4526
                                subid_desc.od_field.pp_path.to_string())))
×
4527
                        .with_reason(
×
4528
                            attr_line_t("unknown value name ")
×
4529
                                .append_quoted(subid_desc.od_field.pp_value))
×
4530
                        .with_snippets(this->get_snippets()));
×
4531
            } else {
4532
                this->lf_desc_fields.insert(iter->first);
758✔
4533
                iter->second->vd_is_desc_field = true;
758✔
4534
            }
4535
        }
4536
    }
4537

4538
    if (this->elf_type == elf_type_t::ELF_TYPE_TEXT
106,736✔
4539
        && this->elf_samples.empty())
53,368✔
4540
    {
4541
        errors.emplace_back(
3✔
4542
            lnav::console::user_message::error(
×
4543
                attr_line_t()
6✔
4544
                    .append_quoted(
3✔
4545
                        lnav::roles::symbol(this->elf_name.to_string()))
6✔
4546
                    .append(" is not a valid log format"))
3✔
4547
                .with_reason("log message samples must be included in a format "
6✔
4548
                             "definition")
4549
                .with_snippets(this->get_snippets()));
6✔
4550
    }
4551

4552
    for (const auto& pat : this->elf_pattern_order) {
151,737✔
4553
        if (this->elf_type != elf_type_t::ELF_TYPE_TEXT) {
98,369✔
4554
            continue;
1✔
4555
        }
4556
        if (pat->p_pcre.pp_value->name_index(this->lf_timestamp_field.get())
98,368✔
4557
            < 0)
98,368✔
4558
        {
4559
            attr_line_t notes;
1✔
4560
            bool first_note = true;
1✔
4561

4562
            if (pat->p_pcre.pp_value->get_capture_count() > 0) {
1✔
4563
                notes.append("the following captures are available:\n  ");
1✔
4564
            }
4565
            for (auto named_cap : pat->p_pcre.pp_value->get_named_captures()) {
4✔
4566
                if (!first_note) {
3✔
4567
                    notes.append(", ");
2✔
4568
                }
4569
                notes.append(
3✔
4570
                    lnav::roles::symbol(named_cap.get_name().to_string()));
6✔
4571
                first_note = false;
3✔
4572
            }
4573
            errors.emplace_back(
1✔
4574
                lnav::console::user_message::error(
×
4575
                    attr_line_t("invalid value for property ")
1✔
4576
                        .append_quoted(lnav::roles::symbol(
2✔
4577
                            fmt::format(FMT_STRING("/{}/timestamp-field"),
4✔
4578
                                        this->elf_name))))
1✔
4579
                    .with_reason(
2✔
4580
                        attr_line_t()
2✔
4581
                            .append_quoted(this->lf_timestamp_field)
1✔
4582
                            .append(" was not found in the pattern at ")
1✔
4583
                            .append(lnav::roles::symbol(pat->p_config_path)))
2✔
4584
                    .with_note(notes)
1✔
4585
                    .with_snippets(this->get_snippets()));
2✔
4586
        }
1✔
4587
    }
4588

4589
    for (size_t sample_index = 0; sample_index < this->elf_samples.size();
241,372✔
4590
         sample_index += 1)
188,004✔
4591
    {
4592
        auto& elf_sample = this->elf_samples[sample_index];
188,004✔
4593
        auto sample_lines
4594
            = string_fragment(elf_sample.s_line.pp_value).split_lines();
188,004✔
4595

4596
        if (this->test_line(elf_sample, errors).is<scan_match>()) {
188,004✔
4597
            for (const auto& pat_name : elf_sample.s_matched_regexes) {
372,974✔
4598
                this->elf_patterns[pat_name]->p_matched_samples.emplace(
184,971✔
4599
                    sample_index);
4600
            }
4601
        }
4602
    }
188,004✔
4603

4604
    if (!this->elf_samples.empty()) {
53,368✔
4605
        for (const auto& elf_sample : this->elf_samples) {
232,453✔
4606
            if (elf_sample.s_matched_regexes.size() <= 1) {
188,004✔
4607
                continue;
188,004✔
4608
            }
4609

4610
            errors.emplace_back(
×
4611
                lnav::console::user_message::warning(
×
4612
                    attr_line_t("invalid log format: ")
×
4613
                        .append_quoted(
×
4614
                            lnav::roles::symbol(this->elf_name.to_string())))
×
4615
                    .with_reason(
×
4616
                        attr_line_t(
×
4617
                            "sample is matched by more than one regex: ")
4618
                            .join(elf_sample.s_matched_regexes,
×
4619
                                  VC_ROLE.value(role_t::VCR_SYMBOL),
×
4620
                                  ", "))
4621
                    .with_snippet(lnav::console::snippet::from(
×
4622
                        elf_sample.s_line.pp_location,
4623
                        attr_line_t().append(lnav::roles::quoted_code(
×
4624
                            elf_sample.s_line.pp_value))))
×
4625
                    .with_help("log format regexes must match a single type "
4626
                               "of log message"));
4627
        }
4628

4629
        for (const auto& pat : this->elf_pattern_order) {
142,815✔
4630
            if (pat->p_matched_samples.empty()) {
98,366✔
4631
                errors.emplace_back(
2✔
4632
                    lnav::console::user_message::warning(
×
4633
                        attr_line_t("invalid pattern: ")
4✔
4634
                            .append_quoted(
2✔
4635
                                lnav::roles::symbol(pat->p_config_path)))
4✔
4636
                        .with_reason("pattern does not match any samples")
4✔
4637
                        .with_snippet(lnav::console::snippet::from(
6✔
4638
                            pat->p_pcre.pp_location, ""))
2✔
4639
                        .with_help(
4640
                            "every pattern should have at least one sample "
4641
                            "that it matches"));
4642
            }
4643
        }
4644
    }
4645

4646
    size_t value_def_index = 0;
53,368✔
4647
    for (auto& elf_value_def : this->elf_value_def_order) {
528,141✔
4648
        elf_value_def->vd_meta.lvm_values_index
474,773✔
4649
            = std::make_optional(value_def_index++);
474,773✔
4650

4651
        if (elf_value_def->vd_meta.lvm_foreign_key
474,773✔
4652
            || elf_value_def->vd_meta.lvm_identifier)
474,773✔
4653
        {
4654
            continue;
254,421✔
4655
        }
4656

4657
        switch (elf_value_def->vd_meta.lvm_kind) {
220,352✔
4658
            case value_kind_t::VALUE_INTEGER:
54,960✔
4659
            case value_kind_t::VALUE_FLOAT:
4660
                this->elf_numeric_value_defs.push_back(elf_value_def);
54,960✔
4661
                break;
54,960✔
4662
            default:
165,392✔
4663
                break;
165,392✔
4664
        }
4665
    }
4666

4667
    int format_index = 0;
53,368✔
4668
    for (auto iter = this->jlf_line_format.begin();
53,368✔
4669
         iter != this->jlf_line_format.end();
168,829✔
4670
         ++iter, format_index++)
115,461✔
4671
    {
4672
        static const intern_string_t ts
4673
            = intern_string::lookup("__timestamp__");
116,977✔
4674
        static const intern_string_t level_field
4675
            = intern_string::lookup("__level__");
116,977✔
4676
        auto& jfe = *iter;
115,461✔
4677

4678
        if (startswith(jfe.jfe_value.pp_value.get(), "/")) {
115,461✔
4679
            jfe.jfe_value.pp_value
4680
                = intern_string::lookup(jfe.jfe_value.pp_value.get() + 1);
192✔
4681
        }
4682
        if (!jfe.jfe_ts_format.empty()) {
115,461✔
4683
            if (!jfe.jfe_value.pp_value.empty() && jfe.jfe_value.pp_value != ts)
854✔
4684
            {
4685
                log_warning(
96✔
4686
                    "%s:line-format[%d]:ignoring field '%s' since "
4687
                    "timestamp-format was used",
4688
                    this->elf_name.get(),
4689
                    format_index,
4690
                    jfe.jfe_value.pp_value.get());
4691
            }
4692
            jfe.jfe_value.pp_value = ts;
854✔
4693
        }
4694

4695
        switch (jfe.jfe_type) {
115,461✔
4696
            case json_log_field::VARIABLE: {
76,591✔
4697
                auto vd_iter
4698
                    = this->elf_value_defs.find(jfe.jfe_value.pp_value);
76,591✔
4699
                if (jfe.jfe_value.pp_value == ts) {
76,591✔
4700
                    this->elf_value_defs[this->lf_timestamp_field]
8,627✔
4701
                        ->vd_meta.lvm_hidden = true;
8,627✔
4702
                } else if (jfe.jfe_value.pp_value == level_field) {
67,964✔
4703
                    this->elf_value_defs[this->elf_level_field]
3,790✔
4704
                        ->vd_meta.lvm_hidden = true;
3,790✔
4705
                } else if (vd_iter == this->elf_value_defs.end()) {
64,174✔
4706
                    errors.emplace_back(
2✔
4707
                        lnav::console::user_message::error(
×
4708
                            attr_line_t("invalid line format element ")
4✔
4709
                                .append_quoted(lnav::roles::symbol(fmt::format(
4✔
4710
                                    FMT_STRING("/{}/line-format/{}/field"),
6✔
4711
                                    this->elf_name,
2✔
4712
                                    format_index))))
4713
                            .with_reason(
4✔
4714
                                attr_line_t()
4✔
4715
                                    .append_quoted(jfe.jfe_value.pp_value)
2✔
4716
                                    .append(" is not a defined value"))
2✔
4717
                            .with_snippet(jfe.jfe_value.to_snippet()));
4✔
4718
                } else {
4719
                    vd_iter->second->vd_line_format_index = format_index;
64,172✔
4720
                    switch (vd_iter->second->vd_meta.lvm_kind) {
64,172✔
4721
                        case value_kind_t::VALUE_INTEGER:
10,804✔
4722
                        case value_kind_t::VALUE_FLOAT:
4723
                            if (jfe.jfe_align
10,804✔
4724
                                == json_format_element::align_t::NONE)
4725
                            {
4726
                                jfe.jfe_align
4727
                                    = json_format_element::align_t::RIGHT;
10,046✔
4728
                            }
4729
                            break;
10,804✔
4730
                        default:
53,368✔
4731
                            break;
53,368✔
4732
                    }
4733
                }
4734
                break;
76,591✔
4735
            }
4736
            case json_log_field::CONSTANT:
38,870✔
4737
                this->jlf_line_format_init_count
38,870✔
4738
                    += std::count(jfe.jfe_default_value.begin(),
38,870✔
4739
                                  jfe.jfe_default_value.end(),
4740
                                  '\n');
38,870✔
4741
                break;
38,870✔
4742
            default:
×
4743
                break;
×
4744
        }
4745
    }
4746

4747
    for (auto& hd_pair : this->elf_highlighter_patterns) {
53,754✔
4748
        auto& hd = hd_pair.second;
386✔
4749
        text_attrs attrs;
386✔
4750

4751
        if (!hd.hd_color.pp_value.empty()) {
386✔
4752
            attrs.ta_fg_color = vc.match_color(
385✔
4753
                styling::color_unit::from_str(hd.hd_color.pp_value)
770✔
4754
                    .unwrapOrElse([&](const auto& msg) {
1✔
4755
                        errors.emplace_back(
1✔
4756
                            lnav::console::user_message::error(
4757
                                attr_line_t()
1✔
4758
                                    .append_quoted(hd.hd_color.pp_value)
2✔
4759
                                    .append(" is not a valid color value for "
1✔
4760
                                            "property ")
4761
                                    .append_quoted(lnav::roles::symbol(
2✔
4762
                                        hd.hd_color.pp_path.to_string())))
1✔
4763
                                .with_reason(msg)
2✔
4764
                                .with_snippet(hd.hd_color.to_snippet()));
1✔
4765
                        return styling::color_unit::EMPTY;
1✔
4766
                    }));
4767
        }
4768

4769
        if (!hd.hd_background_color.pp_value.empty()) {
386✔
4770
            attrs.ta_bg_color = vc.match_color(
1✔
4771
                styling::color_unit::from_str(hd.hd_background_color.pp_value)
2✔
4772
                    .unwrapOrElse([&](const auto& msg) {
1✔
4773
                        errors.emplace_back(
1✔
4774
                            lnav::console::user_message::error(
4775
                                attr_line_t()
1✔
4776
                                    .append_quoted(
2✔
4777
                                        hd.hd_background_color.pp_value)
1✔
4778
                                    .append(" is not a valid color value for "
1✔
4779
                                            "property ")
4780
                                    .append_quoted(lnav::roles::symbol(
2✔
4781
                                        hd.hd_background_color.pp_path
1✔
4782
                                            .to_string())))
4783
                                .with_reason(msg)
2✔
4784
                                .with_snippet(
4785
                                    hd.hd_background_color.to_snippet()));
1✔
4786
                        return styling::color_unit::EMPTY;
1✔
4787
                    }));
4788
        }
4789

4790
        if (hd.hd_underline) {
386✔
4791
            attrs |= text_attrs::style::underline;
96✔
4792
        }
4793
        if (hd.hd_blink) {
386✔
4794
            attrs |= text_attrs::style::blink;
×
4795
        }
4796

4797
        if (hd.hd_pattern.pp_value != nullptr) {
386✔
4798
            this->lf_highlighters.emplace_back(hd.hd_pattern.pp_value);
384✔
4799
            this->lf_highlighters.back()
384✔
4800
                .with_name(hd_pair.first.to_string())
768✔
4801
                .with_attrs(attrs)
384✔
4802
                .with_nestable(hd.hd_nestable);
384✔
4803
        }
4804
    }
4805
}
4806

4807
void
4808
external_log_format::register_vtabs(
44,996✔
4809
    log_vtab_manager* vtab_manager,
4810
    std::vector<lnav::console::user_message>& errors)
4811
{
4812
    for (auto& elf_search_table : this->elf_search_tables) {
54,013✔
4813
        if (elf_search_table.second.std_pattern.pp_value == nullptr) {
9,017✔
4814
            continue;
1✔
4815
        }
4816

4817
        auto lst = std::make_shared<log_search_table>(
4818
            elf_search_table.second.std_pattern.pp_value,
9,016✔
4819
            elf_search_table.first);
9,016✔
4820
        lst->lst_format = this;
9,016✔
4821
        lst->lst_log_path_glob = elf_search_table.second.std_glob;
9,016✔
4822
        if (elf_search_table.second.std_level != LEVEL_UNKNOWN) {
9,016✔
4823
            lst->lst_log_level = elf_search_table.second.std_level;
3,220✔
4824
        }
4825
        auto errmsg = vtab_manager->register_vtab(lst);
9,016✔
4826
        if (!errmsg.empty()) {
9,016✔
4827
#if 0
4828
            errors.push_back("error:" + this->elf_name.to_string() + ":"
4829
                             + search_iter->first.to_string()
4830
                             + ":unable to register table -- " + errmsg);
4831
#endif
4832
        }
4833
    }
9,016✔
4834
}
44,996✔
4835

4836
bool
4837
external_log_format::match_samples(const std::vector<sample_t>& samples) const
3,714,262✔
4838
{
4839
    for (const auto& sample_iter : samples) {
16,639,717✔
4840
        for (const auto& pat_iter : this->elf_pattern_order) {
35,303,802✔
4841
            auto& pat = *pat_iter;
22,378,347✔
4842

4843
            if (!pat.p_pcre.pp_value) {
22,378,347✔
4844
                continue;
×
4845
            }
4846

4847
            if (pat.p_pcre.pp_value
44,756,694✔
4848
                    ->find_in(sample_iter.s_line.pp_value, PCRE2_NO_UTF_CHECK)
44,756,694✔
4849
                    .ignore_error())
44,756,694✔
4850
            {
4851
                return true;
15,766✔
4852
            }
4853
        }
4854
    }
4855

4856
    return false;
3,698,496✔
4857
}
4858

4859
class external_log_table : public log_format_vtab_impl {
4860
public:
4861
    explicit external_log_table(std::shared_ptr<const log_format> elf)
44,996✔
4862
        : log_format_vtab_impl(elf),
44,996✔
4863
          elt_format(dynamic_cast<const external_log_format*>(elf.get()))
44,996✔
4864
    {
4865
    }
44,996✔
4866

4867
    void get_columns(std::vector<vtab_column>& cols) const override
45,387✔
4868
    {
4869
        const auto& elf = this->elt_format;
45,387✔
4870

4871
        cols.resize(elf->elf_column_count);
45,387✔
4872
        for (const auto& vd : elf->elf_value_def_order) {
451,113✔
4873
            auto type_pair = logline_value_to_sqlite_type(vd->vd_meta.lvm_kind);
405,726✔
4874

4875
            if (!vd->vd_meta.lvm_column.is<logline_value_meta::table_column>())
405,726✔
4876
            {
4877
                continue;
41,851✔
4878
            }
4879

4880
            auto col
4881
                = vd->vd_meta.lvm_column.get<logline_value_meta::table_column>()
363,875✔
4882
                      .value;
363,875✔
4883
            require(0 <= col && col < elf->elf_column_count);
363,875✔
4884

4885
            cols[col].vc_name = vd->vd_meta.lvm_name.get();
363,875✔
4886
            cols[col].vc_type = type_pair.first;
363,875✔
4887
            cols[col].vc_subtype = type_pair.second;
363,875✔
4888
            cols[col].vc_collator = vd->vd_collate;
363,875✔
4889
            cols[col].vc_comment = vd->vd_description;
363,875✔
4890
        }
4891
    }
45,387✔
4892

4893
    void get_foreign_keys(
35,900✔
4894
        std::unordered_set<std::string>& keys_inout) const override
4895
    {
4896
        log_vtab_impl::get_foreign_keys(keys_inout);
35,900✔
4897

4898
        for (const auto& elf_value_def : this->elt_format->elf_value_defs) {
518,276✔
4899
            if (elf_value_def.second->vd_meta.lvm_foreign_key
482,376✔
4900
                || elf_value_def.second->vd_meta.lvm_identifier)
482,376✔
4901
            {
4902
                keys_inout.emplace(elf_value_def.first.to_string());
177,932✔
4903
            }
4904
        }
4905
    }
35,900✔
4906

4907
    bool next(log_cursor& lc, logfile_sub_source& lss) override
3,479✔
4908
    {
4909
        if (lc.is_eof()) {
3,479✔
4910
            return true;
×
4911
        }
4912

4913
        content_line_t cl(lss.at(lc.lc_curr_line));
3,479✔
4914
        auto* lf = lss.find_file_ptr(cl);
3,479✔
4915
        auto lf_iter = lf->begin() + cl;
3,479✔
4916
        if (lf_iter->is_continued()) {
3,479✔
4917
            return false;
×
4918
        }
4919

4920
        if (lf->get_format_name() == this->lfvi_format->get_name()) {
3,479✔
4921
            return true;
3,473✔
4922
        }
4923

4924
        return false;
6✔
4925
    }
4926

4927
    void extract(logfile* lf,
3,395✔
4928
                 uint64_t line_number,
4929
                 string_attrs_t& sa,
4930
                 logline_value_vector& values) override
4931
    {
4932
        auto format = lf->get_format();
3,395✔
4933

4934
        sa.clear();
3,395✔
4935
        format->annotate(lf, line_number, sa, values);
3,395✔
4936
    }
3,395✔
4937

4938
    const external_log_format* elt_format;
4939
    line_range elt_container_body;
4940
};
4941

4942
std::shared_ptr<log_vtab_impl>
4943
external_log_format::get_vtab_impl() const
44,996✔
4944
{
4945
    return std::make_shared<external_log_table>(this->shared_from_this());
44,996✔
4946
}
4947

4948
std::shared_ptr<log_format>
4949
external_log_format::specialized(int fmt_lock)
514✔
4950
{
4951
    auto retval = std::make_shared<external_log_format>(*this);
514✔
4952

4953
    retval->lf_specialized = true;
514✔
4954
    if (this->elf_type == elf_type_t::ELF_TYPE_JSON) {
514✔
4955
        this->jlf_parse_context
4956
            = std::make_shared<yajlpp_parse_context>(this->elf_name);
47✔
4957
        this->jlf_yajl_handle.reset(
47✔
4958
            yajl_alloc(&this->jlf_parse_context->ypc_callbacks,
47✔
4959
                       nullptr,
4960
                       this->jlf_parse_context.get()),
47✔
4961
            yajl_handle_deleter());
4962
        yajl_config(this->jlf_yajl_handle.get(), yajl_dont_validate_strings, 1);
47✔
4963
        this->jlf_cached_line.reserve(16 * 1024);
47✔
4964
    }
4965

4966
    this->elf_specialized_value_defs_state = *this->elf_value_defs_state;
514✔
4967

4968
    return retval;
1,028✔
4969
}
514✔
4970

4971
log_format::match_name_result
4972
external_log_format::match_name(const std::string& filename)
848,732✔
4973
{
4974
    if (this->elf_filename_pcre.pp_value == nullptr) {
848,732✔
4975
        return name_matched{};
847,333✔
4976
    }
4977

4978
    if (this->elf_filename_pcre.pp_value->find_in(filename)
2,798✔
4979
            .ignore_error()
2,798✔
4980
            .has_value())
1,399✔
4981
    {
4982
        return name_matched{};
247✔
4983
    }
4984

4985
    return name_mismatched{
2,304✔
4986
        this->elf_filename_pcre.pp_value->match_partial(filename),
2,304✔
4987
        this->elf_filename_pcre.pp_value->get_pattern(),
1,152✔
4988
    };
1,152✔
4989
}
4990

4991
auto
4992
external_log_format::value_line_count(scan_batch_context& sbc,
246,648✔
4993
                                      const value_def* vd,
4994
                                      bool top_level,
4995
                                      std::optional<double> val,
4996
                                      const unsigned char* str,
4997
                                      ssize_t len,
4998
                                      yajl_string_props_t* props)
4999
    -> value_line_count_result
5000
{
5001
    value_line_count_result retval;
246,648✔
5002

5003
    if (vd == nullptr) {
246,648✔
5004
        if (this->jlf_hide_extra || !top_level) {
224,880✔
5005
            retval.vlcr_count = 0;
210,941✔
5006
        }
5007

5008
        return retval;
224,880✔
5009
    }
5010

5011
    if (str != nullptr && props != nullptr && !val) {
21,768✔
5012
        auto frag = string_fragment::from_bytes(str, len);
19,353✔
5013
        while (frag.endswith("\n")) {
19,561✔
5014
            frag.pop_back();
208✔
5015
            props->line_feeds -= 1;
208✔
5016
        }
5017
        retval.vlcr_has_ansi |= props->has_ansi;
19,353✔
5018
        retval.vlcr_count += props->line_feeds;
19,353✔
5019
    }
5020

5021
    if (vd->vd_meta.lvm_values_index) {
21,768✔
5022
        auto& lvs = sbc.sbc_value_stats[vd->vd_meta.lvm_values_index.value()];
6,608✔
5023
        if (len > lvs.lvs_width) {
6,608✔
5024
            lvs.lvs_width = len;
3,469✔
5025
        }
5026
        if (val) {
6,608✔
5027
            lvs.add_value(val.value());
168✔
5028
        }
5029
    }
5030

5031
    if (vd->vd_line_format_index) {
21,768✔
5032
        retval.vlcr_line_format_count += 1;
3,264✔
5033
        retval.vlcr_count -= 1;
3,264✔
5034
        retval.vlcr_line_format_index = vd->vd_line_format_index;
3,264✔
5035
    }
5036
    if (vd->vd_meta.is_hidden()) {
21,768✔
5037
        retval.vlcr_count = 0;
3,109✔
5038
        return retval;
3,109✔
5039
    }
5040

5041
    return retval;
18,659✔
5042
}
5043

5044
log_level_t
5045
external_log_format::convert_level(string_fragment sf,
195,324✔
5046
                                   scan_batch_context* sbc) const
5047
{
5048
    auto retval = LEVEL_INFO;
195,324✔
5049

5050
    if (sf.is_valid()) {
195,324✔
5051
        if (sbc != nullptr) {
153,223✔
5052
            auto ssm_res = sbc->sbc_level_cache.lookup(sf);
10,138✔
5053
            if (ssm_res.has_value()) {
10,138✔
5054
                return static_cast<log_level_t>(ssm_res.value());
4,123✔
5055
            }
5056
        }
5057

5058
        if (this->elf_level_patterns.empty()) {
149,100✔
5059
            retval = string2level(sf.data(), sf.length());
32,977✔
5060
        } else {
5061
            for (const auto& elf_level_pattern : this->elf_level_patterns) {
313,256✔
5062
                if (elf_level_pattern.second.lp_pcre.pp_value
573,748✔
5063
                        ->find_in(sf, PCRE2_NO_UTF_CHECK)
573,748✔
5064
                        .ignore_error()
573,748✔
5065
                        .has_value())
286,874✔
5066
                {
5067
                    retval = elf_level_pattern.first;
89,741✔
5068
                    break;
89,741✔
5069
                }
5070
            }
5071
        }
5072

5073
        if (sbc != nullptr
149,100✔
5074
            && sf.length() <= lnav::small_string_map::MAX_KEY_SIZE)
149,100✔
5075
        {
5076
            sbc->sbc_level_cache.insert(sf, retval);
5,363✔
5077
        }
5078
    }
5079

5080
    return retval;
191,201✔
5081
}
5082

5083
logline_value_meta
5084
external_log_format::get_value_meta(intern_string_t field_name,
15,287✔
5085
                                    value_kind_t kind)
5086
{
5087
    const auto iter = this->elf_value_defs.find(field_name);
15,287✔
5088
    if (iter == this->elf_value_defs.end()) {
15,287✔
5089
        auto retval = logline_value_meta(
5090
            field_name, kind, logline_value_meta::external_column{}, this);
980✔
5091

5092
        retval.lvm_hidden = this->jlf_hide_extra;
980✔
5093
        return retval;
980✔
5094
    }
980✔
5095

5096
    auto lvm = iter->second->vd_meta;
14,307✔
5097

5098
    lvm.lvm_kind = kind;
14,307✔
5099
    return lvm;
14,307✔
5100
}
14,307✔
5101

5102
logline_value_meta
5103
external_log_format::get_value_meta(yajlpp_parse_context* ypc,
2,422✔
5104
                                    const value_def* vd,
5105
                                    value_kind_t kind)
5106
{
5107
    if (vd == nullptr) {
2,422✔
5108
        auto retval = logline_value_meta(
5109
            ypc->get_path(), kind, logline_value_meta::external_column{}, this);
57✔
5110

5111
        retval.lvm_hidden = this->jlf_hide_extra;
57✔
5112
        return retval;
57✔
5113
    }
57✔
5114

5115
    auto lvm = vd->vd_meta;
2,365✔
5116

5117
    lvm.lvm_kind = kind;
2,365✔
5118
    return lvm;
2,365✔
5119
}
2,365✔
5120

5121
void
5122
external_log_format::json_append(const log_format_file_state& lffs,
8,897✔
5123
                                 const json_format_element& jfe,
5124
                                 const value_def* vd,
5125
                                 const string_fragment& sf)
5126
{
5127
    if (jfe.jfe_align == json_format_element::align_t::RIGHT) {
8,897✔
5128
        auto sf_width = sf.column_width();
318✔
5129
        if (sf_width < jfe.jfe_min_width) {
318✔
5130
            this->json_append_to_cache(jfe.jfe_min_width - sf_width);
×
5131
        } else if (jfe.jfe_auto_width && vd != nullptr
24✔
5132
                   && sf_width
342✔
5133
                       < lffs.lffs_value_stats[vd->vd_meta.lvm_values_index
24✔
5134
                                                   .value()]
24✔
5135
                             .lvs_width)
24✔
5136
        {
5137
            this->json_append_to_cache(
8✔
5138
                lffs.lffs_value_stats[vd->vd_meta.lvm_values_index.value()]
8✔
5139
                    .lvs_width
8✔
5140
                - sf_width);
8✔
5141
        }
5142
    }
5143
    this->json_append_to_cache(sf.data(), sf.length());
8,897✔
5144
    if ((jfe.jfe_align == json_format_element::align_t::LEFT
8,897✔
5145
         || jfe.jfe_align == json_format_element::align_t::NONE)
8,843✔
5146
        && (jfe.jfe_min_width > 0 || jfe.jfe_auto_width))
8,579✔
5147
    {
5148
        auto sf_width = sf.column_width();
1,273✔
5149
        if (sf_width < jfe.jfe_min_width) {
1,273✔
5150
            this->json_append_to_cache(jfe.jfe_min_width - sf_width);
374✔
5151
        } else if (jfe.jfe_auto_width && vd != nullptr
773✔
5152
                   && sf_width
1,672✔
5153
                       < lffs.lffs_value_stats[vd->vd_meta.lvm_values_index
684✔
5154
                                                   .value()]
684✔
5155
                             .lvs_width)
684✔
5156
        {
5157
            this->json_append_to_cache(
×
5158
                lffs.lffs_value_stats[vd->vd_meta.lvm_values_index.value()]
×
5159
                    .lvs_width
×
5160
                - sf_width);
×
5161
        }
5162
    }
5163
}
8,897✔
5164

5165
intern_string_t
5166
external_log_format::get_pattern_name(const pattern_locks& pl,
50✔
5167
                                      uint64_t line_number) const
5168
{
5169
    if (this->elf_type != elf_type_t::ELF_TYPE_TEXT) {
50✔
5170
        static auto structured = intern_string::lookup("structured");
5171

5172
        return structured;
×
5173
    }
5174
    auto pat_index = pl.pattern_index_for_line(line_number);
50✔
5175
    return this->elf_pattern_order[pat_index]->p_name;
50✔
5176
}
5177

5178
std::string
5179
log_format::get_pattern_path(const pattern_locks& pl,
×
5180
                             uint64_t line_number) const
5181
{
5182
    auto pat_index = pl.pattern_index_for_line(line_number);
×
5183
    return fmt::format(FMT_STRING("builtin ({})"), pat_index);
×
5184
}
5185

5186
intern_string_t
5187
log_format::get_pattern_name(const pattern_locks& pl,
6✔
5188
                             uint64_t line_number) const
5189
{
5190
    char pat_str[128];
5191

5192
    auto pat_index = pl.pattern_index_for_line(line_number);
6✔
5193
    auto to_n_res = fmt::format_to_n(
×
5194
        pat_str, sizeof(pat_str) - 1, FMT_STRING("builtin ({})"), pat_index);
18✔
5195
    pat_str[to_n_res.size] = '\0';
6✔
5196
    return intern_string::lookup(pat_str);
12✔
5197
}
5198

5199
std::shared_ptr<log_format>
5200
log_format::find_root_format(const char* name)
1,298✔
5201
{
5202
    auto& fmts = get_root_formats();
1,298✔
5203
    for (auto& lf : fmts) {
50,951✔
5204
        if (lf->get_name() == name) {
50,951✔
5205
            return lf;
1,298✔
5206
        }
5207
    }
5208
    return nullptr;
×
5209
}
5210

5211
exttm
5212
log_format::tm_for_display(logfile::iterator ll, string_fragment sf)
974✔
5213
{
5214
    auto adjusted_time = ll->get_timeval();
974✔
5215
    exttm retval;
974✔
5216

5217
    retval.et_nsec = std::chrono::duration_cast<std::chrono::nanoseconds>(
974✔
5218
                         std::chrono::microseconds{adjusted_time.tv_usec})
×
5219
                         .count();
974✔
5220
    if (this->lf_timestamp_flags & ETF_NANOS_SET) {
974✔
5221
        timeval actual_tv;
5222
        exttm tm;
×
5223
        if (this->lf_date_time.scan(sf.data(),
×
5224
                                    sf.length(),
×
5225
                                    this->get_timestamp_formats(),
5226
                                    &tm,
5227
                                    actual_tv,
5228
                                    false))
5229
        {
5230
            adjusted_time.tv_usec = actual_tv.tv_usec;
×
5231
            retval.et_nsec = tm.et_nsec;
×
5232
        }
5233
    }
5234
    gmtime_r(&adjusted_time.tv_sec, &retval.et_tm);
974✔
5235
    retval.et_flags = this->lf_timestamp_flags;
974✔
5236
    if (this->lf_timestamp_flags & ETF_ZONE_SET
974✔
5237
        && this->lf_date_time.dts_zoned_to_local)
884✔
5238
    {
5239
        retval.et_flags &= ~ETF_Z_IS_UTC;
884✔
5240
    }
5241
    retval.et_gmtoff = this->lf_date_time.dts_local_offset_cache;
974✔
5242

5243
    return retval;
1,948✔
5244
}
5245

5246
pattern_for_lines::pattern_for_lines(uint32_t pfl_line, uint32_t pfl_pat_index)
2,907✔
5247
    : pfl_line(pfl_line), pfl_pat_index(pfl_pat_index)
2,907✔
5248
{
5249
}
2,907✔
5250

5251
void
5252
logline_value_stats::merge(const logline_value_stats& other)
15,353✔
5253
{
5254
    if (other.lvs_count == 0) {
15,353✔
5255
        return;
14,219✔
5256
    }
5257

5258
    require(other.lvs_min_value <= other.lvs_max_value);
1,134✔
5259

5260
    if (other.lvs_width > this->lvs_width) {
1,134✔
5261
        this->lvs_width = other.lvs_width;
753✔
5262
    }
5263
    if (other.lvs_min_value < this->lvs_min_value) {
1,134✔
5264
        this->lvs_min_value = other.lvs_min_value;
772✔
5265
    }
5266
    if (other.lvs_max_value > this->lvs_max_value) {
1,134✔
5267
        this->lvs_max_value = other.lvs_max_value;
1,063✔
5268
    }
5269
    this->lvs_count += other.lvs_count;
1,134✔
5270
    this->lvs_total += other.lvs_total;
1,134✔
5271
    this->lvs_tdigest.insert(other.lvs_tdigest);
1,134✔
5272
    ensure(this->lvs_count >= 0);
1,134✔
5273
    ensure(this->lvs_min_value <= this->lvs_max_value);
1,134✔
5274
}
5275

5276
void
5277
logline_value_stats::add_value(double value)
31,437✔
5278
{
5279
    if (value < this->lvs_min_value) {
31,437✔
5280
        this->lvs_min_value = value;
1,656✔
5281
    }
5282
    if (value > this->lvs_max_value) {
31,437✔
5283
        this->lvs_max_value = value;
2,364✔
5284
    }
5285
    this->lvs_count += 1;
31,437✔
5286
    this->lvs_total += value;
31,437✔
5287
    this->lvs_tdigest.insert(value);
31,437✔
5288
}
31,437✔
5289

5290
std::vector<logline_value_meta>
5291
external_log_format::get_value_metadata() const
9,016✔
5292
{
5293
    std::vector<logline_value_meta> retval;
9,016✔
5294

5295
    for (const auto& vd : this->elf_value_def_order) {
94,668✔
5296
        retval.emplace_back(vd->vd_meta);
85,652✔
5297
    }
5298

5299
    return retval;
9,016✔
5300
}
×
5301

5302
std::optional<size_t>
5303
external_log_format::stats_index_for_value(const intern_string_t& name) const
72✔
5304
{
5305
    const auto iter = this->elf_value_defs.find(name);
72✔
5306
    if (iter != this->elf_value_defs.end()
72✔
5307
        && iter->second->vd_meta.lvm_values_index)
72✔
5308
    {
5309
        return iter->second->vd_meta.lvm_values_index.value();
72✔
5310
    }
5311

5312
    return std::nullopt;
×
5313
}
5314

5315
std::string
5316
external_log_format::get_pattern_regex(const pattern_locks& pl,
17✔
5317
                                       uint64_t line_number) const
5318
{
5319
    if (this->elf_type != elf_type_t::ELF_TYPE_TEXT) {
17✔
5320
        return "";
2✔
5321
    }
5322
    auto pat_index = pl.pattern_index_for_line(line_number);
16✔
5323
    return this->elf_pattern_order[pat_index]->p_pcre.pp_value->get_pattern();
16✔
5324
}
5325

5326
bool
5327
external_log_format::hide_field(const intern_string_t field_name, bool val)
12✔
5328
{
5329
    const auto vd_iter = this->elf_value_defs.find(field_name);
12✔
5330
    if (vd_iter == this->elf_value_defs.end()) {
12✔
5331
        log_warning("field to hide not found: %s.%s",
1✔
5332
                    this->elf_name.c_str(),
5333
                    field_name.c_str());
5334
        return false;
1✔
5335
    }
5336

5337
    vd_iter->second->vd_meta.lvm_user_hidden = val;
11✔
5338
    if (this->elf_type == elf_type_t::ELF_TYPE_JSON) {
11✔
5339
        bool found = false;
2✔
5340

5341
        if (!field_name.to_string_fragment().find('#')) {
2✔
5342
            for (const auto& jfe : this->jlf_line_format) {
18✔
5343
                if (jfe.jfe_value.pp_value == field_name) {
17✔
5344
                    log_debug(
×
5345
                        "hide-field not triggering rebuild since it is in "
5346
                        "line-format: %s.%s",
5347
                        this->elf_name.c_str(),
5348
                        field_name.c_str());
5349
                    found = true;
×
5350
                    break;
×
5351
                }
5352
            }
5353
        }
5354
        if (!found) {
2✔
5355
            log_info("format field %s.%s changed, rebuilding",
2✔
5356
                     this->elf_name.get(),
5357
                     field_name.get());
5358
            this->elf_value_defs_state->vds_generation += 1;
2✔
5359
        }
5360
    }
5361
    return true;
11✔
5362
}
5363

5364
bool
5365
external_log_format::format_changed()
2,634✔
5366
{
5367
    if (this->elf_specialized_value_defs_state.vds_generation
5,268✔
5368
        != this->elf_value_defs_state->vds_generation)
2,634✔
5369
    {
5370
        this->elf_specialized_value_defs_state = *this->elf_value_defs_state;
2✔
5371
        this->jlf_cached_offset = -1;
2✔
5372
        return true;
2✔
5373
    }
5374

5375
    return false;
2,632✔
5376
}
5377

5378
bool
5379
format_tag_def::path_restriction::matches(const char* fn) const
5✔
5380
{
5381
    return fnmatch(this->p_glob.c_str(), fn, 0) == 0;
5✔
5382
}
5383

5384
bool
5385
format_partition_def::path_restriction::matches(const char* fn) const
5✔
5386
{
5387
    return fnmatch(this->p_glob.c_str(), fn, 0) == 0;
5✔
5388
}
5389

5390
int
5391
pattern_locks::pattern_index_for_line(uint64_t line_number) const
6,828✔
5392
{
5393
    if (this->pl_lines.empty()) {
6,828✔
5394
        return -1;
×
5395
    }
5396

5397
    auto iter
5398
        = std::lower_bound(this->pl_lines.cbegin(),
6,828✔
5399
                           this->pl_lines.cend(),
5400
                           line_number,
5401
                           [](const pattern_for_lines& pfl, uint32_t line) {
6,887✔
5402
                               return pfl.pfl_line < line;
6,887✔
5403
                           });
5404

5405
    if (iter == this->pl_lines.end() || iter->pfl_line != line_number) {
6,828✔
5406
        --iter;
6,182✔
5407
    }
5408

5409
    return iter->pfl_pat_index;
6,828✔
5410
}
5411

5412
/* XXX */
5413
#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