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

tstack / lnav / 18660673143-2587

20 Oct 2025 06:02PM UTC coverage: 69.917% (-0.003%) from 69.92%
18660673143-2587

push

github

tstack
[strace] improve strace_log support

76 of 96 new or added lines in 4 files covered. (79.17%)

12 existing lines in 1 file now uncovered.

50771 of 72616 relevant lines covered (69.92%)

421954.95 hits per line

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

88.69
/src/log_format_impls.cc
1
/**
2
 * Copyright (c) 2007-2017, 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
 * @file log_format_impls.cc
30
 */
31

32
#include <algorithm>
33
#include <chrono>
34
#include <memory>
35
#include <utility>
36

37
#include "log_format.hh"
38

39
#include <stdio.h>
40

41
#include "base/injector.bind.hh"
42
#include "base/opt_util.hh"
43
#include "base/string_attr_type.hh"
44
#include "config.h"
45
#include "formats/logfmt/logfmt.parser.hh"
46
#include "log_vtab_impl.hh"
47
#include "ptimec.hh"
48
#include "scn/scan.h"
49
#include "sql_util.hh"
50
#include "yajlpp/yajlpp.hh"
51

52
class piper_log_format : public log_format {
53
public:
54
    const intern_string_t get_name() const override
13,567✔
55
    {
56
        static const intern_string_t RETVAL
57
            = intern_string::lookup("lnav_piper_log");
15,003✔
58

59
        return RETVAL;
13,567✔
60
    }
61

62
    scan_result_t scan(logfile& lf,
10,709✔
63
                       std::vector<logline>& dst,
64
                       const line_info& li,
65
                       shared_buffer_ref& sbr,
66
                       scan_batch_context& sbc) override
67
    {
68
        if (lf.has_line_metadata()
10,709✔
69
            && lf.get_text_format() == text_format_t::TF_LOG)
10,709✔
70
        {
71
            dst.emplace_back(
213✔
72
                li.li_file_range.fr_offset, li.li_timestamp, li.li_level);
213✔
73
            return scan_match{1};
213✔
74
        }
75

76
        return scan_no_match{"not a piper capture"};
10,496✔
77
    }
78

79
    static constexpr int TIMESTAMP_SIZE = 28;
80

81
    void annotate(logfile* lf,
41✔
82
                  uint64_t line_number,
83
                  string_attrs_t& sa,
84
                  logline_value_vector& values,
85
                  bool annotate_module) const override
86
    {
87
        auto lr = line_range{0, TIMESTAMP_SIZE};
41✔
88
        sa.emplace_back(lr, L_TIMESTAMP.value());
41✔
89
        log_format::annotate(lf, line_number, sa, values, annotate_module);
41✔
90
    }
41✔
91

92
    void get_subline(const logline& ll,
317✔
93
                     shared_buffer_ref& sbr,
94
                     subline_options opts) override
95
    {
96
        this->plf_cached_line.resize(TIMESTAMP_SIZE);
317✔
97
        auto tlen = sql_strftime(this->plf_cached_line.data(),
317✔
98
                                 this->plf_cached_line.size(),
99
                                 ll.get_timeval(),
317✔
100
                                 'T');
101
        this->plf_cached_line.resize(tlen);
317✔
102
        {
103
            char zone_str[16];
104
            exttm tmptm;
317✔
105

106
            tmptm.et_flags |= ETF_ZONE_SET;
317✔
107
            tmptm.et_gmtoff
108
                = lnav::local_time_to_info(
634✔
109
                      date::local_seconds{ll.get_time<std::chrono::seconds>()})
317✔
110
                      .first.offset.count();
317✔
111
            off_t zone_len = 0;
317✔
112
            ftime_z(zone_str, zone_len, sizeof(zone_str), tmptm);
317✔
113
            for (off_t lpc = 0; lpc < zone_len; lpc++) {
1,902✔
114
                this->plf_cached_line.push_back(zone_str[lpc]);
1,585✔
115
            }
116
        }
117
        this->plf_cached_line.push_back(' ');
317✔
118
        const auto prefix_len = this->plf_cached_line.size();
317✔
119
        this->plf_cached_line.resize(this->plf_cached_line.size()
634✔
120
                                     + sbr.length());
317✔
121
        memcpy(
317✔
122
            &this->plf_cached_line[prefix_len], sbr.get_data(), sbr.length());
317✔
123

124
        sbr.share(this->plf_share_manager,
634✔
125
                  this->plf_cached_line.data(),
317✔
126
                  this->plf_cached_line.size());
127
    }
317✔
128

129
    std::shared_ptr<log_format> specialized(int fmt_lock) override
6✔
130
    {
131
        auto retval = std::make_shared<piper_log_format>(*this);
6✔
132

133
        retval->lf_specialized = true;
6✔
134
        retval->lf_timestamp_flags |= ETF_ZONE_SET | ETF_MICROS_SET;
6✔
135
        return retval;
12✔
136
    }
6✔
137

138
private:
139
    shared_buffer plf_share_manager;
140
    std::vector<char> plf_cached_line;
141
};
142

143
class generic_log_format : public log_format {
144
public:
145
    static const pcre_format* get_pcre_log_formats()
10,764✔
146
    {
147
        static const pcre_format log_fmt[] = {
148
            pcre_format(R"(^(?:\*\*\*\s+)?(?<timestamp>@[0-9a-zA-Z]{16,24}))"),
149
            pcre_format(
150
                R"(^(?:\*\*\*\s+)?(?<timestamp>(?:\s|\d{4}[\-\/]\d{2}[\-\/]\d{2}|T|\d{1,2}:\d{2}(?::\d{2}(?:[\.,]\d{1,6})?)?|Z|[+\-]\d{2}:?\d{2}|(?!DBG|DEBUG|ERR|INFO|WARN|NONE)[A-Z]{3,4})+)[:|\s]?(trc|trace|dbg|debug|info|warn(?:ing)?|err(?:or)?)[:|\s]\s*)"),
151
            pcre_format(
152
                R"(^(?:\*\*\*\s+)?(?<timestamp>[\w:+ \.,+/-]+) \[(trace|debug|info|warn(?:ing)?|error|critical)\]\s+)"),
153
            pcre_format(
154
                R"(^(?:\*\*\*\s+)?(?<timestamp>[\w:+ \.,+/-]+) -- (trace|debug|info|warn(?:ing)?|error|critical) --\s+)"),
155

156
            pcre_format(R"(^(?:\*\*\*\s+)?(?<timestamp>[\w:+/\.-]+) \[\w\s+)"),
157
            pcre_format(R"(^(?:\*\*\*\s+)?(?<timestamp>[\w:+,/\.-]+)\s+)"),
158
            pcre_format(R"(^(?:\*\*\*\s+)?(?<timestamp>[\w:+,/\.-]+) -\s+)"),
159
            pcre_format(R"(^(?:\*\*\*\s+)?(?<timestamp>[\w:+ \.,/-]+) -\s+)"),
160
            pcre_format(
161
                R"(^(?:\*\*\*\s+)?\[(?<timestamp>[\w:+ \.,+/-]+)\] \[(trace|debug|info|warn(?:ing)?|error|critical)\]\s+)"),
162
            pcre_format("^(?:\\*\\*\\*\\s+)?(?<timestamp>[\\w: "
163
                        "\\.,/-]+)\\[[^\\]]+\\]\\s+"),
164
            pcre_format(R"(^(?:\*\*\*\s+)?(?<timestamp>[\w:+ \.,/-]+)\s+)"),
165

166
            pcre_format(
167
                R"(^(?:\*\*\*\s+)?\[(?<timestamp>[\w:+ \.,+/-]+)\]\s*(\w+):?\s+)"),
168
            pcre_format(
169
                R"(^(?:\*\*\*\s+)?\[(?<timestamp>[\w:+ \.,+/-]+)\]\s+)"),
170
            pcre_format("^(?:\\*\\*\\*\\s+)?\\[(?<timestamp>[\\w: "
171
                        "\\.,+/-]+)\\] \\w+\\s+"),
172
            pcre_format("^(?:\\*\\*\\*\\s+)?\\[(?<timestamp>[\\w: ,+/-]+)\\] "
173
                        "\\(\\d+\\)\\s+"),
174

175
            pcre_format(),
176
        };
10,764✔
177

178
        return log_fmt;
10,764✔
179
    }
180

181
    std::string get_pattern_regex(uint64_t line_number) const override
×
182
    {
183
        int pat_index = this->pattern_index_for_line(line_number);
×
184
        return get_pcre_log_formats()[pat_index].name;
×
185
    }
186

187
    const intern_string_t get_name() const override
13,095✔
188
    {
189
        static const intern_string_t RETVAL
190
            = intern_string::lookup("generic_log");
14,531✔
191

192
        return RETVAL;
13,095✔
193
    }
194

195
    scan_result_t scan(logfile& lf,
10,672✔
196
                       std::vector<logline>& dst,
197
                       const line_info& li,
198
                       shared_buffer_ref& sbr,
199
                       scan_batch_context& sbc) override
200
    {
201
        exttm log_time;
10,672✔
202
        timeval log_tv;
203
        string_fragment ts;
10,672✔
204
        std::optional<string_fragment> level;
10,672✔
205
        const char* last_pos;
206

207
        if (dst.empty()) {
10,672✔
208
            auto file_options = lf.get_file_options();
192✔
209

210
            if (file_options) {
192✔
211
                this->lf_date_time.dts_default_zone
212
                    = file_options->second.fo_default_zone.pp_value;
2✔
213
            } else {
214
                this->lf_date_time.dts_default_zone = nullptr;
190✔
215
            }
216
        }
192✔
217

218
        if ((last_pos = this->log_scanf(dst.size(),
10,672✔
219
                                        sbr.to_string_fragment(),
220
                                        get_pcre_log_formats(),
221
                                        nullptr,
222
                                        &log_time,
223
                                        &log_tv,
224

225
                                        &ts,
226
                                        &level))
227
            != nullptr)
10,672✔
228
        {
229
            auto level_val = log_level_t::LEVEL_UNKNOWN;
1,525✔
230
            if (level) {
1,525✔
231
                level_val = string2level(level->data(), level->length());
1,525✔
232
            }
233

234
            if (!((log_time.et_flags & ETF_DAY_SET)
1,525✔
235
                  && (log_time.et_flags & ETF_MONTH_SET)
1,450✔
236
                  && (log_time.et_flags & ETF_YEAR_SET)))
1,450✔
237
            {
238
                this->check_for_new_year(dst, log_time, log_tv);
686✔
239
            }
240

241
            if (!(this->lf_timestamp_flags
3,050✔
242
                  & (ETF_MILLIS_SET | ETF_MICROS_SET | ETF_NANOS_SET))
1,525✔
243
                && !dst.empty()
1,200✔
244
                && dst.back().get_time<std::chrono::seconds>().count()
1,198✔
245
                    == log_tv.tv_sec
1,198✔
246
                && dst.back()
3,587✔
247
                        .get_subsecond_time<std::chrono::microseconds>()
2,387✔
248
                        .count()
862✔
249
                    != 0)
250
            {
251
                auto log_ms
252
                    = dst.back()
×
253
                          .get_subsecond_time<std::chrono::microseconds>();
×
254

255
                log_time.et_nsec
256
                    = std::chrono::duration_cast<std::chrono::nanoseconds>(
×
257
                          log_ms)
258
                          .count();
×
259
                log_tv.tv_usec
260
                    = std::chrono::duration_cast<std::chrono::microseconds>(
×
261
                          log_ms)
262
                          .count();
×
263
            }
264

265
            auto tid_iter = sbc.sbc_tids.insert_tid(
1,525✔
266
                sbc.sbc_allocator, string_fragment{}, log_tv);
×
267
            tid_iter->second.titr_level_stats.update_msg_count(level_val);
1,525✔
268
            dst.emplace_back(li.li_file_range.fr_offset, log_tv, level_val);
1,525✔
269
            return scan_match{5};
1,525✔
270
        }
271

272
        return scan_no_match{"no patterns matched"};
9,147✔
273
    }
274

275
    void annotate(logfile* lf,
92✔
276
                  uint64_t line_number,
277
                  string_attrs_t& sa,
278
                  logline_value_vector& values,
279
                  bool annotate_module) const override
280
    {
281
        thread_local auto md = lnav::pcre2pp::match_data::unitialized();
92✔
282
        auto& line = values.lvv_sbr;
92✔
283
        int pat_index = this->pattern_index_for_line(line_number);
92✔
284
        const auto& fmt = get_pcre_log_formats()[pat_index];
92✔
285
        int prefix_len = 0;
92✔
286
        const auto line_sf = line.to_string_fragment();
92✔
287
        auto match_res = fmt.pcre->capture_from(line_sf)
92✔
288
                             .into(md)
92✔
289
                             .matches(PCRE2_NO_UTF_CHECK)
184✔
290
                             .ignore_error();
92✔
291
        if (!match_res) {
92✔
292
            return;
8✔
293
        }
294

295
        auto ts_cap = md[fmt.pf_timestamp_index].value();
84✔
296
        auto lr = to_line_range(ts_cap.trim());
84✔
297
        sa.emplace_back(lr, L_TIMESTAMP.value());
84✔
298

299
        values.lvv_values.emplace_back(TS_META, line, lr);
84✔
300
        values.lvv_values.back().lv_meta.lvm_format = (log_format*) this;
84✔
301

302
        prefix_len = md[0]->sf_end;
84✔
303
        auto level_cap = md[2];
84✔
304
        if (level_cap) {
84✔
305
            if (string2level(level_cap->data(), level_cap->length(), true)
78✔
306
                != LEVEL_UNKNOWN)
78✔
307
            {
308
                values.lvv_values.emplace_back(
78✔
309
                    LEVEL_META, line, to_line_range(level_cap->trim()));
78✔
310
                values.lvv_values.back().lv_meta.lvm_format
78✔
311
                    = (log_format*) this;
78✔
312

313
                lr = to_line_range(level_cap->trim());
78✔
314
                if (lr.lr_end != (ssize_t) line.length()) {
78✔
315
                    sa.emplace_back(lr, L_LEVEL.value());
78✔
316
                }
317
            }
318
        }
319

320
        lr.lr_start = 0;
84✔
321
        lr.lr_end = prefix_len;
84✔
322
        sa.emplace_back(lr, L_PREFIX.value());
84✔
323

324
        lr.lr_start = prefix_len;
84✔
325
        lr.lr_end = line.length();
84✔
326
        sa.emplace_back(lr, SA_BODY.value());
84✔
327

328
        log_format::annotate(lf, line_number, sa, values, annotate_module);
84✔
329
    }
330

331
    std::shared_ptr<log_format> specialized(int fmt_lock) override
50✔
332
    {
333
        auto retval = std::make_shared<generic_log_format>(*this);
50✔
334

335
        retval->lf_specialized = true;
50✔
336
        return retval;
100✔
337
    }
50✔
338

339
    bool hide_field(const intern_string_t field_name, bool val) override
2✔
340
    {
341
        if (field_name == TS_META.lvm_name) {
2✔
342
            TS_META.lvm_user_hidden = val;
1✔
343
            return true;
1✔
344
        }
345
        if (field_name == LEVEL_META.lvm_name) {
1✔
346
            LEVEL_META.lvm_user_hidden = val;
1✔
347
            return true;
1✔
348
        }
349
        if (field_name == OPID_META.lvm_name) {
×
350
            OPID_META.lvm_user_hidden = val;
×
351
            return true;
×
352
        }
353
        return false;
×
354
    }
355

356
    std::map<intern_string_t, logline_value_meta> get_field_states() override
125✔
357
    {
358
        return {
359
            {TS_META.lvm_name, TS_META},
360
            {LEVEL_META.lvm_name, LEVEL_META},
361
            {OPID_META.lvm_name, OPID_META},
362
        };
625✔
363
    }
125✔
364

365
private:
366
    static logline_value_meta TS_META;
367
    static logline_value_meta LEVEL_META;
368
    static logline_value_meta OPID_META;
369
};
370

371
logline_value_meta generic_log_format::TS_META{
372
    intern_string::lookup("log_time"),
373
    value_kind_t::VALUE_TEXT,
374
    logline_value_meta::table_column{2},
375
};
376

377
logline_value_meta generic_log_format::LEVEL_META{
378
    intern_string::lookup("log_level"),
379
    value_kind_t::VALUE_TEXT,
380
    logline_value_meta::table_column{3},
381
};
382

383
logline_value_meta generic_log_format::OPID_META{
384
    intern_string::lookup("log_opid"),
385
    value_kind_t::VALUE_TEXT,
386
    logline_value_meta::internal_column{},
387
};
388

389
std::string
390
from_escaped_string(const char* str, size_t len)
22✔
391
{
392
    std::string retval;
22✔
393

394
    for (size_t lpc = 0; lpc < len; lpc++) {
44✔
395
        switch (str[lpc]) {
22✔
396
            case '\\':
22✔
397
                if ((lpc + 3) < len && str[lpc + 1] == 'x') {
22✔
398
                    int ch;
399

400
                    if (sscanf(&str[lpc + 2], "%2x", &ch) == 1) {
22✔
401
                        retval.append(1, (char) ch & 0xff);
22✔
402
                        lpc += 3;
22✔
403
                    }
404
                }
405
                break;
22✔
406
            default:
×
407
                retval.append(1, str[lpc]);
×
408
                break;
×
409
        }
410
    }
411

412
    return retval;
22✔
413
}
×
414

415
std::optional<const char*>
416
lnav_strnstr(const char* s, const char* find, size_t slen)
1,577,794✔
417
{
418
    char c, sc;
419
    size_t len;
420

421
    if ((c = *find++) != '\0') {
1,577,794✔
422
        len = strlen(find);
1,577,794✔
423
        do {
424
            do {
425
                if (slen < 1 || (sc = *s) == '\0') {
6,762,578✔
426
                    return std::nullopt;
856,710✔
427
                }
428
                --slen;
5,905,868✔
429
                ++s;
5,905,868✔
430
            } while (sc != c);
5,905,868✔
431
            if (len > slen) {
721,084✔
432
                return std::nullopt;
×
433
            }
434
        } while (strncmp(s, find, len) != 0);
721,084✔
435
        s--;
721,084✔
436
    }
437
    return s;
721,084✔
438
}
439

440
struct separated_string {
441
    const char* ss_str;
442
    size_t ss_len;
443
    const char* ss_separator;
444
    size_t ss_separator_len;
445

446
    separated_string(const char* str, size_t len)
34,124✔
447
        : ss_str(str), ss_len(len), ss_separator(","),
34,124✔
448
          ss_separator_len(strlen(this->ss_separator))
34,124✔
449
    {
450
    }
34,124✔
451

452
    separated_string& with_separator(const char* sep)
34,124✔
453
    {
454
        this->ss_separator = sep;
34,124✔
455
        this->ss_separator_len = strlen(sep);
34,124✔
456
        return *this;
34,124✔
457
    }
458

459
    struct iterator {
460
        const separated_string& i_parent;
461
        const char* i_pos;
462
        const char* i_next_pos;
463
        size_t i_index;
464

465
        iterator(const separated_string& ss, const char* pos)
822,944✔
466
            : i_parent(ss), i_pos(pos), i_next_pos(pos), i_index(0)
822,944✔
467
        {
468
            this->update();
822,944✔
469
        }
822,944✔
470

471
        void update()
1,577,794✔
472
        {
473
            const separated_string& ss = this->i_parent;
1,577,794✔
474
            auto next_field
475
                = lnav_strnstr(this->i_pos,
1,577,794✔
476
                               ss.ss_separator,
1,577,794✔
477
                               ss.ss_len - (this->i_pos - ss.ss_str));
1,577,794✔
478
            if (next_field) {
1,577,794✔
479
                this->i_next_pos = next_field.value() + ss.ss_separator_len;
721,084✔
480
            } else {
481
                this->i_next_pos = ss.ss_str + ss.ss_len;
856,710✔
482
            }
483
        }
1,577,794✔
484

485
        iterator& operator++()
754,850✔
486
        {
487
            this->i_pos = this->i_next_pos;
754,850✔
488
            this->update();
754,850✔
489
            this->i_index += 1;
754,850✔
490

491
            return *this;
754,850✔
492
        }
493

494
        string_fragment operator*()
678,232✔
495
        {
496
            const auto& ss = this->i_parent;
678,232✔
497
            int end;
498

499
            if (this->i_next_pos < (ss.ss_str + ss.ss_len)) {
678,232✔
500
                end = this->i_next_pos - ss.ss_str - ss.ss_separator_len;
648,502✔
501
            } else {
502
                end = this->i_next_pos - ss.ss_str;
29,730✔
503
            }
504
            return string_fragment::from_byte_range(
678,232✔
505
                ss.ss_str, this->i_pos - ss.ss_str, end);
678,232✔
506
        }
507

508
        bool operator==(const iterator& other) const
788,820✔
509
        {
510
            return (&this->i_parent == &other.i_parent)
788,820✔
511
                && (this->i_pos == other.i_pos);
788,820✔
512
        }
513

514
        bool operator!=(const iterator& other) const
788,666✔
515
        {
516
            return !(*this == other);
788,666✔
517
        }
518

519
        size_t index() const { return this->i_index; }
1,626,498✔
520
    };
521

522
    iterator begin() { return {*this, this->ss_str}; }
34,124✔
523

524
    iterator end() { return {*this, this->ss_str + this->ss_len}; }
788,820✔
525
};
526

527
class bro_log_format : public log_format {
528
public:
529
    static const intern_string_t TS;
530
    static const intern_string_t DURATION;
531
    struct field_def {
532
        logline_value_meta fd_meta;
533
        logline_value_meta* fd_root_meta;
534
        std::string fd_collator;
535
        std::optional<size_t> fd_numeric_index;
536

537
        explicit field_def(const intern_string_t name,
622✔
538
                           size_t col,
539
                           log_format* format)
540
            : fd_meta(name,
1,244✔
541
                      value_kind_t::VALUE_TEXT,
542
                      logline_value_meta::table_column{col},
622✔
543
                      format),
544
              fd_root_meta(&FIELD_META.find(name)->second)
622✔
545
        {
546
        }
622✔
547

548
        field_def& with_kind(value_kind_t kind,
458✔
549
                             bool identifier = false,
550
                             bool foreign_key = false,
551
                             const std::string& collator = "")
552
        {
553
            this->fd_meta.lvm_kind = kind;
458✔
554
            this->fd_meta.lvm_identifier = identifier;
458✔
555
            this->fd_meta.lvm_foreign_key = foreign_key;
458✔
556
            this->fd_collator = collator;
458✔
557
            return *this;
458✔
558
        }
559

560
        field_def& with_numeric_index(size_t index)
116✔
561
        {
562
            this->fd_numeric_index = index;
116✔
563
            return *this;
116✔
564
        }
565
    };
566

567
    static std::unordered_map<const intern_string_t, logline_value_meta>
568
        FIELD_META;
569

570
    static const intern_string_t get_opid_desc()
2,590✔
571
    {
572
        static const intern_string_t RETVAL = intern_string::lookup("std");
4,038✔
573

574
        return RETVAL;
2,590✔
575
    }
576

577
    bro_log_format()
724✔
578
    {
724✔
579
        this->lf_structured = true;
724✔
580
        this->lf_is_self_describing = true;
724✔
581
        this->lf_time_ordered = false;
724✔
582
        this->lf_timestamp_point_of_reference
583
            = timestamp_point_of_reference_t::start;
724✔
584

585
        auto desc_v = std::make_shared<std::vector<opid_descriptor>>();
724✔
586
        desc_v->emplace({});
724✔
587
        this->lf_opid_description_def->emplace(get_opid_desc(),
1,448✔
588
                                               opid_descriptors{desc_v});
1,448✔
589
    }
724✔
590

591
    const intern_string_t get_name() const override
114,606✔
592
    {
593
        static const intern_string_t name(intern_string::lookup("bro"));
116,042✔
594

595
        return this->blf_format_name.empty() ? name : this->blf_format_name;
114,606✔
596
    }
597

598
    void clear() override
10,731✔
599
    {
600
        this->log_format::clear();
10,731✔
601
        this->blf_format_name.clear();
10,731✔
602
        this->blf_field_defs.clear();
10,731✔
603
    }
10,731✔
604

605
    scan_result_t scan_int(std::vector<logline>& dst,
4,168✔
606
                           const line_info& li,
607
                           shared_buffer_ref& sbr,
608
                           scan_batch_context& sbc)
609
    {
610
        static const intern_string_t STATUS_CODE
611
            = intern_string::lookup("bro_status_code");
4,210✔
612
        static const intern_string_t UID = intern_string::lookup("bro_uid");
4,210✔
613
        static const intern_string_t ID_ORIG_H
614
            = intern_string::lookup("bro_id_orig_h");
4,210✔
615

616
        separated_string ss(sbr.get_data(), sbr.length());
4,168✔
617
        timeval tv;
618
        exttm tm;
4,168✔
619
        auto found_ts = false;
4,168✔
620
        log_level_t level = LEVEL_INFO;
4,168✔
621
        uint16_t opid = 0;
4,168✔
622
        auto opid_cap = string_fragment::invalid();
4,168✔
623
        auto host_cap = string_fragment::invalid();
4,168✔
624
        auto duration = std::chrono::microseconds{0};
4,168✔
625

626
        ss.with_separator(this->blf_separator.get());
4,168✔
627

628
        for (auto iter = ss.begin(); iter != ss.end(); ++iter) {
122,914✔
629
            if (iter.index() == 0 && *iter == "#close") {
118,768✔
630
                return scan_match{2000};
22✔
631
            }
632

633
            if (iter.index() >= this->blf_field_defs.size()) {
118,746✔
634
                break;
×
635
            }
636

637
            const auto& fd = this->blf_field_defs[iter.index()];
118,746✔
638

639
            if (TS == fd.fd_meta.lvm_name) {
118,746✔
640
                static const char* const TIME_FMT[] = {"%s.%f"};
641
                const auto sf = *iter;
4,146✔
642

643
                if (this->lf_date_time.scan(
4,146✔
644
                        sf.data(), sf.length(), TIME_FMT, &tm, tv))
4,146✔
645
                {
646
                    this->lf_timestamp_flags = tm.et_flags;
4,146✔
647
                    found_ts = true;
4,146✔
648
                }
649
            } else if (STATUS_CODE == fd.fd_meta.lvm_name) {
114,600✔
650
                const auto sf = *iter;
3,960✔
651

652
                if (!sf.empty() && sf[0] >= '4') {
3,960✔
653
                    level = LEVEL_ERROR;
20✔
654
                }
655
            } else if (UID == fd.fd_meta.lvm_name) {
110,640✔
656
                opid_cap = *iter;
4,146✔
657

658
                opid = opid_cap.hash();
4,146✔
659
            } else if (ID_ORIG_H == fd.fd_meta.lvm_name) {
106,494✔
660
                host_cap = *iter;
4,146✔
661
            } else if (DURATION == fd.fd_meta.lvm_name) {
102,348✔
662
                const auto sf = *iter;
186✔
663
                auto scan_res = scn::scan<double>("{}", sf.to_string_view());
186✔
664
                if (scan_res) {
186✔
NEW
665
                    duration = std::chrono::microseconds{
×
666
                        static_cast<long long>(scan_res->value() * 1000000)};
667
                }
668
            }
669

670
            if (fd.fd_numeric_index) {
118,746✔
671
                switch (fd.fd_meta.lvm_kind) {
21,288✔
672
                    case value_kind_t::VALUE_INTEGER:
21,288✔
673
                    case value_kind_t::VALUE_FLOAT: {
674
                        const auto sv = (*iter).to_string_view();
21,288✔
675
                        auto scan_float_res = scn::scan_value<double>(sv);
21,288✔
676
                        if (scan_float_res) {
21,288✔
677
                            this->lf_value_stats[fd.fd_numeric_index.value()]
17,328✔
678
                                .add_value(scan_float_res->value());
17,328✔
679
                        }
680
                        break;
21,288✔
681
                    }
682
                    default:
×
683
                        break;
×
684
                }
685
            }
686
        }
687

688
        if (found_ts) {
4,146✔
689
            if (!this->lf_specialized) {
4,146✔
690
                for (auto& ll : dst) {
198✔
691
                    ll.set_ignore(true);
176✔
692
                }
693
            }
694

695
            if (opid_cap.is_valid()) {
4,146✔
696
                auto opid_iter = sbc.sbc_opids.insert_op(
4,146✔
697
                    sbc.sbc_allocator,
698
                    opid_cap,
699
                    tv,
700
                    this->lf_timestamp_point_of_reference,
701
                    duration);
702
                opid_iter->second.otr_level_stats.update_msg_count(level);
4,146✔
703

704
                auto& otr = opid_iter->second;
4,146✔
705
                if (!otr.otr_description.lod_id && host_cap.is_valid()
6,012✔
706
                    && otr.otr_description.lod_elements.empty())
6,012✔
707
                {
708
                    otr.otr_description.lod_id = get_opid_desc();
1,866✔
709
                    otr.otr_description.lod_elements.emplace_back(
3,732✔
710
                        0, host_cap.to_string());
1,866✔
711
                }
712
            }
713
            dst.emplace_back(li.li_file_range.fr_offset, tv, level, 0, opid);
4,146✔
714
            dst.back().set_opid(opid);
4,146✔
715
            return scan_match{2000};
4,146✔
716
        }
717
        return scan_no_match{"no header found"};
×
718
    }
719

720
    scan_result_t scan(logfile& lf,
10,709✔
721
                       std::vector<logline>& dst,
722
                       const line_info& li,
723
                       shared_buffer_ref& sbr,
724
                       scan_batch_context& sbc) override
725
    {
726
        static const auto SEP_RE
727
            = lnav::pcre2pp::code::from_const(R"(^#separator\s+(.+))");
10,709✔
728

729
        if (dst.empty()) {
10,709✔
730
            auto file_options = lf.get_file_options();
1,101✔
731

732
            if (file_options) {
1,101✔
733
                this->lf_date_time.dts_default_zone
734
                    = file_options->second.fo_default_zone.pp_value;
53✔
735
            } else {
736
                this->lf_date_time.dts_default_zone = nullptr;
1,048✔
737
            }
738
        }
1,101✔
739

740
        if (!this->blf_format_name.empty()) {
10,709✔
741
            return this->scan_int(dst, li, sbr, sbc);
4,146✔
742
        }
743

744
        if (dst.empty() || dst.size() > 20 || sbr.empty()
12,025✔
745
            || sbr.get_data()[0] == '#')
12,025✔
746
        {
747
            return scan_no_match{"no header found"};
4,113✔
748
        }
749

750
        auto line_iter = dst.begin();
2,450✔
751
        auto read_result = lf.read_line(line_iter);
2,450✔
752

753
        if (read_result.isErr()) {
2,450✔
754
            return scan_no_match{"unable to read first line"};
×
755
        }
756

757
        auto line = read_result.unwrap();
2,450✔
758
        auto md = SEP_RE.create_match_data();
2,450✔
759

760
        auto match_res = SEP_RE.capture_from(line.to_string_fragment())
2,450✔
761
                             .into(md)
2,450✔
762
                             .matches(PCRE2_NO_UTF_CHECK)
4,900✔
763
                             .ignore_error();
2,450✔
764
        if (!match_res) {
2,450✔
765
            return scan_no_match{"cannot read separator header"};
2,428✔
766
        }
767

768
        this->clear();
22✔
769

770
        auto sep = from_escaped_string(md[1]->data(), md[1]->length());
22✔
771
        this->blf_separator = intern_string::lookup(sep);
22✔
772

773
        for (++line_iter; line_iter != dst.end(); ++line_iter) {
176✔
774
            auto next_read_result = lf.read_line(line_iter);
154✔
775

776
            if (next_read_result.isErr()) {
154✔
777
                return scan_no_match{"unable to read header line"};
×
778
            }
779

780
            line = next_read_result.unwrap();
154✔
781
            separated_string ss(line.get_data(), line.length());
154✔
782

783
            ss.with_separator(this->blf_separator.get());
154✔
784
            auto iter = ss.begin();
154✔
785

786
            string_fragment directive = *iter;
154✔
787

788
            if (directive.empty() || directive[0] != '#') {
154✔
789
                continue;
×
790
            }
791

792
            ++iter;
154✔
793
            if (iter == ss.end()) {
154✔
794
                continue;
×
795
            }
796

797
            if (directive == "#set_separator") {
154✔
798
                this->blf_set_separator = intern_string::lookup(*iter);
22✔
799
            } else if (directive == "#empty_field") {
132✔
800
                this->blf_empty_field = intern_string::lookup(*iter);
22✔
801
            } else if (directive == "#unset_field") {
110✔
802
                this->blf_unset_field = intern_string::lookup(*iter);
22✔
803
            } else if (directive == "#path") {
88✔
804
                auto full_name = fmt::format(FMT_STRING("bro_{}_log"), *iter);
66✔
805
                this->blf_format_name = intern_string::lookup(full_name);
22✔
806
            } else if (directive == "#fields" && this->blf_field_defs.empty()) {
88✔
807
                do {
808
                    auto field_name
809
                        = intern_string::lookup("bro_" + sql_safe_ident(*iter));
622✔
810
                    auto common_iter = FIELD_META.find(field_name);
622✔
811
                    if (common_iter == FIELD_META.end()) {
622✔
812
                        FIELD_META.emplace(field_name,
616✔
813
                                           logline_value_meta{
1,232✔
814
                                               field_name,
815
                                               value_kind_t::VALUE_TEXT,
816
                                           });
817
                    }
818
                    this->blf_field_defs.emplace_back(
1,244✔
819
                        field_name, this->blf_field_defs.size(), this);
622✔
820
                    ++iter;
622✔
821
                } while (iter != ss.end());
622✔
822
            } else if (directive == "#types") {
44✔
823
                static const char* KNOWN_IDS[] = {
824
                    "bro_conn_uids",
825
                    "bro_fuid",
826
                    "bro_host",
827
                    "bro_info_code",
828
                    "bro_method",
829
                    "bro_mime_type",
830
                    "bro_orig_fuids",
831
                    "bro_parent_fuid",
832
                    "bro_proto",
833
                    "bro_referrer",
834
                    "bro_resp_fuids",
835
                    "bro_service",
836
                    "bro_uid",
837
                    "bro_uri",
838
                    "bro_user_agent",
839
                    "bro_username",
840
                };
841
                static const char* KNOWN_FOREIGN[] = {
842
                    "bro_status_code",
843
                };
844

845
                int numeric_count = 0;
22✔
846

847
                do {
848
                    string_fragment field_type = *iter;
622✔
849
                    auto& fd = this->blf_field_defs[iter.index() - 1];
622✔
850

851
                    if (field_type == "time") {
622✔
852
                        fd.with_kind(value_kind_t::VALUE_TIMESTAMP);
44✔
853
                    } else if (field_type == "string") {
600✔
854
                        bool ident = std::binary_search(std::begin(KNOWN_IDS),
456✔
855
                                                        std::end(KNOWN_IDS),
856
                                                        fd.fd_meta.lvm_name);
228✔
857
                        fd.with_kind(value_kind_t::VALUE_TEXT, ident);
456✔
858
                    } else if (field_type == "count") {
372✔
859
                        bool ident = std::binary_search(std::begin(KNOWN_IDS),
228✔
860
                                                        std::end(KNOWN_IDS),
861
                                                        fd.fd_meta.lvm_name);
114✔
862
                        bool foreign
863
                            = std::binary_search(std::begin(KNOWN_FOREIGN),
228✔
864
                                                 std::end(KNOWN_FOREIGN),
865
                                                 fd.fd_meta.lvm_name);
114✔
866
                        fd.with_kind(
228✔
867
                              value_kind_t::VALUE_INTEGER, ident, foreign)
868
                            .with_numeric_index(numeric_count);
114✔
869
                        numeric_count += 1;
114✔
870
                    } else if (field_type == "bool") {
258✔
871
                        fd.with_kind(value_kind_t::VALUE_BOOLEAN);
8✔
872
                    } else if (field_type == "addr") {
254✔
873
                        fd.with_kind(
88✔
874
                            value_kind_t::VALUE_TEXT, true, false, "ipaddress");
875
                    } else if (field_type == "port") {
210✔
876
                        fd.with_kind(value_kind_t::VALUE_INTEGER, true);
88✔
877
                    } else if (field_type == "interval") {
166✔
878
                        fd.with_kind(value_kind_t::VALUE_FLOAT)
4✔
879
                            .with_numeric_index(numeric_count);
2✔
880
                        numeric_count += 1;
2✔
881
                    }
882

883
                    ++iter;
622✔
884
                } while (iter != ss.end());
622✔
885

886
                this->lf_value_stats.resize(numeric_count);
22✔
887
            }
888
        }
154✔
889

890
        if (!this->blf_format_name.empty() && !this->blf_separator.empty()
44✔
891
            && !this->blf_field_defs.empty())
44✔
892
        {
893
            return this->scan_int(dst, li, sbr, sbc);
22✔
894
        }
895

896
        this->blf_format_name.clear();
×
897
        this->lf_value_stats.clear();
×
898

899
        return scan_no_match{"no header found"};
×
900
    }
2,450✔
901

902
    void annotate(logfile* lf,
29,802✔
903
                  uint64_t line_number,
904
                  string_attrs_t& sa,
905
                  logline_value_vector& values,
906
                  bool annotate_module) const override
907
    {
908
        static const intern_string_t UID = intern_string::lookup("bro_uid");
29,840✔
909

910
        auto& sbr = values.lvv_sbr;
29,802✔
911
        separated_string ss(sbr.get_data(), sbr.length());
29,802✔
912

913
        ss.with_separator(this->blf_separator.get());
29,802✔
914

915
        for (auto iter = ss.begin(); iter != ss.end(); ++iter) {
664,508✔
916
            if (iter.index() >= this->blf_field_defs.size()) {
634,910✔
917
                return;
204✔
918
            }
919

920
            const field_def& fd = this->blf_field_defs[iter.index()];
634,706✔
921
            string_fragment sf = *iter;
634,706✔
922

923
            if (sf == this->blf_empty_field) {
634,706✔
924
                sf.clear();
29,605✔
925
            } else if (sf == this->blf_unset_field) {
605,101✔
926
                sf.invalidate();
69,086✔
927
            }
928

929
            auto lr = line_range(sf.sf_begin, sf.sf_end);
634,706✔
930

931
            if (fd.fd_meta.lvm_name == TS) {
634,706✔
932
                sa.emplace_back(lr, L_TIMESTAMP.value());
29,802✔
933
            } else if (fd.fd_meta.lvm_name == UID) {
604,904✔
934
                sa.emplace_back(lr, L_OPID.value());
29,802✔
935
                values.lvv_opid_value = sf.to_string();
29,802✔
936
                values.lvv_opid_provenance
937
                    = logline_value_vector::opid_provenance::file;
29,802✔
938
            }
939

940
            if (lr.is_valid()) {
634,706✔
941
                values.lvv_values.emplace_back(fd.fd_meta, sbr, lr);
565,620✔
942
            } else {
943
                values.lvv_values.emplace_back(fd.fd_meta);
69,086✔
944
            }
945
            values.lvv_values.back().lv_meta.lvm_user_hidden
634,706✔
946
                = fd.fd_root_meta->lvm_user_hidden;
634,706✔
947
        }
948

949
        log_format::annotate(lf, line_number, sa, values, annotate_module);
29,598✔
950
    }
951

952
    const logline_value_stats* stats_for_value(
36✔
953
        const intern_string_t& name) const override
954
    {
955
        const logline_value_stats* retval = nullptr;
36✔
956

957
        for (const auto& blf_field_def : this->blf_field_defs) {
540✔
958
            if (blf_field_def.fd_meta.lvm_name == name) {
540✔
959
                if (!blf_field_def.fd_numeric_index) {
36✔
960
                    break;
×
961
                }
962
                retval = &this->lf_value_stats[blf_field_def.fd_numeric_index
963
                                                   .value()];
36✔
964
                break;
36✔
965
            }
966
        }
967

968
        return retval;
36✔
969
    }
970

971
    bool hide_field(intern_string_t field_name, bool val) override
2✔
972
    {
973
        if (field_name == LOG_TIME_STR) {
2✔
974
            field_name = TS;
×
975
        }
976

977
        auto fd_iter = FIELD_META.find(field_name);
2✔
978
        if (fd_iter == FIELD_META.end()) {
2✔
979
            return false;
×
980
        }
981

982
        fd_iter->second.lvm_user_hidden = val;
2✔
983

984
        return true;
2✔
985
    }
986

987
    std::map<intern_string_t, logline_value_meta> get_field_states() override
125✔
988
    {
989
        std::map<intern_string_t, logline_value_meta> retval;
125✔
990

991
        for (const auto& fd : FIELD_META) {
473✔
992
            retval.emplace(fd.first, fd.second);
348✔
993
        }
994

995
        return retval;
125✔
996
    }
×
997

998
    std::shared_ptr<log_format> specialized(int fmt_lock = -1) override
22✔
999
    {
1000
        auto retval = std::make_shared<bro_log_format>(*this);
22✔
1001

1002
        retval->lf_specialized = true;
22✔
1003
        return retval;
44✔
1004
    }
22✔
1005

1006
    class bro_log_table : public log_format_vtab_impl {
1007
    public:
1008
        explicit bro_log_table(const bro_log_format& format)
20✔
1009
            : log_format_vtab_impl(format), blt_format(format)
20✔
1010
        {
1011
        }
20✔
1012

1013
        void get_columns(std::vector<vtab_column>& cols) const override
29✔
1014
        {
1015
            for (const auto& fd : this->blt_format.blf_field_defs) {
854✔
1016
                auto type_pair = log_vtab_impl::logline_value_to_sqlite_type(
825✔
1017
                    fd.fd_meta.lvm_kind);
825✔
1018

1019
                cols.emplace_back(fd.fd_meta.lvm_name.to_string(),
825✔
1020
                                  type_pair.first,
1021
                                  fd.fd_collator,
825✔
1022
                                  false,
1,650✔
1023
                                  "",
1024
                                  type_pair.second);
1025
            }
1026
        }
29✔
1027

1028
        void get_foreign_keys(
10✔
1029
            std::unordered_set<std::string>& keys_inout) const override
1030
        {
1031
            this->log_vtab_impl::get_foreign_keys(keys_inout);
10✔
1032

1033
            for (const auto& fd : this->blt_format.blf_field_defs) {
292✔
1034
                if (fd.fd_meta.lvm_identifier || fd.fd_meta.lvm_foreign_key) {
282✔
1035
                    keys_inout.emplace(fd.fd_meta.lvm_name.to_string());
123✔
1036
                }
1037
            }
1038
        }
10✔
1039

1040
        const bro_log_format& blt_format;
1041
    };
1042

1043
    static std::map<intern_string_t, std::shared_ptr<bro_log_table>>&
1044
    get_tables()
20✔
1045
    {
1046
        static std::map<intern_string_t, std::shared_ptr<bro_log_table>> retval;
20✔
1047

1048
        return retval;
20✔
1049
    }
1050

1051
    std::shared_ptr<log_vtab_impl> get_vtab_impl() const override
624✔
1052
    {
1053
        if (this->blf_format_name.empty()) {
624✔
1054
            return nullptr;
604✔
1055
        }
1056

1057
        std::shared_ptr<bro_log_table> retval = nullptr;
20✔
1058

1059
        auto& tables = get_tables();
20✔
1060
        const auto iter = tables.find(this->blf_format_name);
20✔
1061
        if (iter == tables.end()) {
20✔
1062
            retval = std::make_shared<bro_log_table>(*this);
20✔
1063
            tables[this->blf_format_name] = retval;
20✔
1064
        }
1065

1066
        return retval;
20✔
1067
    }
20✔
1068

1069
    void get_subline(const logline& ll,
33,810✔
1070
                     shared_buffer_ref& sbr,
1071
                     subline_options opts) override
1072
    {
1073
    }
33,810✔
1074

1075
    intern_string_t blf_format_name;
1076
    intern_string_t blf_separator;
1077
    intern_string_t blf_set_separator;
1078
    intern_string_t blf_empty_field;
1079
    intern_string_t blf_unset_field;
1080
    std::vector<field_def> blf_field_defs;
1081
};
1082

1083
std::unordered_map<const intern_string_t, logline_value_meta>
1084
    bro_log_format::FIELD_META;
1085

1086
const intern_string_t bro_log_format::TS = intern_string::lookup("bro_ts");
1087
const intern_string_t bro_log_format::DURATION
1088
    = intern_string::lookup("bro_duration");
1089

1090
struct ws_separated_string {
1091
    const char* ss_str;
1092
    size_t ss_len;
1093

1094
    explicit ws_separated_string(const char* str = nullptr, size_t len = -1)
19,491✔
1095
        : ss_str(str), ss_len(len)
19,491✔
1096
    {
1097
    }
19,491✔
1098

1099
    struct iterator {
1100
        enum class state_t {
1101
            NORMAL,
1102
            QUOTED,
1103
        };
1104

1105
        const ws_separated_string& i_parent;
1106
        const char* i_pos;
1107
        const char* i_next_pos;
1108
        size_t i_index{0};
1109
        state_t i_state{state_t::NORMAL};
1110

1111
        iterator(const ws_separated_string& ss, const char* pos)
30,605✔
1112
            : i_parent(ss), i_pos(pos), i_next_pos(pos)
30,605✔
1113
        {
1114
            this->update();
30,605✔
1115
        }
30,605✔
1116

1117
        void update()
41,171✔
1118
        {
1119
            const auto& ss = this->i_parent;
41,171✔
1120
            bool done = false;
41,171✔
1121

1122
            while (!done && this->i_next_pos < (ss.ss_str + ss.ss_len)) {
333,747✔
1123
                switch (this->i_state) {
292,576✔
1124
                    case state_t::NORMAL:
285,818✔
1125
                        if (*this->i_next_pos == '"') {
285,818✔
1126
                            this->i_state = state_t::QUOTED;
255✔
1127
                        } else if (isspace(*this->i_next_pos)) {
285,563✔
1128
                            done = true;
25,761✔
1129
                        }
1130
                        break;
285,818✔
1131
                    case state_t::QUOTED:
6,758✔
1132
                        if (*this->i_next_pos == '"') {
6,758✔
1133
                            this->i_state = state_t::NORMAL;
255✔
1134
                        }
1135
                        break;
6,758✔
1136
                }
1137
                if (!done) {
292,576✔
1138
                    this->i_next_pos += 1;
266,815✔
1139
                }
1140
            }
1141
        }
41,171✔
1142

1143
        iterator& operator++()
10,566✔
1144
        {
1145
            const auto& ss = this->i_parent;
10,566✔
1146

1147
            this->i_pos = this->i_next_pos;
10,566✔
1148
            while (this->i_pos < (ss.ss_str + ss.ss_len)
10,566✔
1149
                   && isspace(*this->i_pos))
20,590✔
1150
            {
1151
                this->i_pos += 1;
10,024✔
1152
                this->i_next_pos += 1;
10,024✔
1153
            }
1154
            this->update();
10,566✔
1155
            this->i_index += 1;
10,566✔
1156

1157
            return *this;
10,566✔
1158
        }
1159

1160
        string_fragment operator*()
26,989✔
1161
        {
1162
            const auto& ss = this->i_parent;
26,989✔
1163
            int end = this->i_next_pos - ss.ss_str;
26,989✔
1164

1165
            return string_fragment(ss.ss_str, this->i_pos - ss.ss_str, end);
26,989✔
1166
        }
1167

1168
        bool operator==(const iterator& other) const
11,114✔
1169
        {
1170
            return (&this->i_parent == &other.i_parent)
11,114✔
1171
                && (this->i_pos == other.i_pos);
11,114✔
1172
        }
1173

1174
        bool operator!=(const iterator& other) const
8,545✔
1175
        {
1176
            return !(*this == other);
8,545✔
1177
        }
1178

1179
        size_t index() const { return this->i_index; }
15,809✔
1180
    };
1181

1182
    iterator begin() { return {*this, this->ss_str}; }
19,491✔
1183

1184
    iterator end() { return {*this, this->ss_str + this->ss_len}; }
11,114✔
1185
};
1186

1187
class w3c_log_format : public log_format {
1188
public:
1189
    static const intern_string_t F_DATE;
1190
    static const intern_string_t F_TIME;
1191

1192
    struct field_def {
1193
        const intern_string_t fd_name;
1194
        logline_value_meta fd_meta;
1195
        logline_value_meta* fd_root_meta{nullptr};
1196
        std::string fd_collator;
1197
        std::optional<size_t> fd_numeric_index;
1198

1199
        explicit field_def(const intern_string_t name)
14✔
1200
            : fd_name(name), fd_meta(intern_string::lookup(sql_safe_ident(
28✔
1201
                                         name.to_string_fragment())),
28✔
1202
                                     value_kind_t::VALUE_TEXT)
14✔
1203
        {
1204
        }
14✔
1205

1206
        field_def(const intern_string_t name, logline_value_meta meta)
59✔
1207
            : fd_name(name), fd_meta(meta)
59✔
1208
        {
1209
        }
59✔
1210

1211
        field_def(size_t col,
9,424✔
1212
                  const char* name,
1213
                  value_kind_t kind,
1214
                  bool ident = false,
1215
                  bool foreign_key = false,
1216
                  std::string coll = "")
1217
            : fd_name(intern_string::lookup(name)),
18,848✔
1218
              fd_meta(
18,848✔
1219
                  intern_string::lookup(sql_safe_ident(string_fragment(name))),
18,848✔
1220
                  kind,
1221
                  logline_value_meta::table_column{col}),
9,424✔
1222
              fd_collator(std::move(coll))
9,424✔
1223
        {
1224
            this->fd_meta.lvm_identifier = ident;
9,424✔
1225
            this->fd_meta.lvm_foreign_key = foreign_key;
9,424✔
1226
        }
9,424✔
1227

1228
        field_def& with_kind(value_kind_t kind,
1229
                             bool identifier = false,
1230
                             const std::string& collator = "")
1231
        {
1232
            this->fd_meta.lvm_kind = kind;
1233
            this->fd_meta.lvm_identifier = identifier;
1234
            this->fd_collator = collator;
1235
            return *this;
1236
        }
1237

1238
        field_def& with_numeric_index(int index)
27✔
1239
        {
1240
            this->fd_numeric_index = index;
27✔
1241
            return *this;
27✔
1242
        }
1243
    };
1244

1245
    static std::unordered_map<const intern_string_t, logline_value_meta>
1246
        FIELD_META;
1247

1248
    struct field_to_struct_t {
1249
        field_to_struct_t(const char* prefix, const char* struct_name)
2,356✔
1250
            : fs_prefix(prefix),
2,356✔
1251
              fs_struct_name(intern_string::lookup(struct_name))
4,712✔
1252
        {
1253
        }
2,356✔
1254

1255
        const char* fs_prefix;
1256
        intern_string_t fs_struct_name;
1257
    };
1258

1259
    static const std::array<field_def, 16>& get_known_fields()
602✔
1260
    {
1261
        static size_t KNOWN_FIELD_INDEX = 0;
1262
        static const std::array<field_def, 16> RETVAL = {
1263
            field_def{
1264
                KNOWN_FIELD_INDEX++,
1265
                "cs-method",
1266
                value_kind_t::VALUE_TEXT,
1267
                true,
1268
            },
1269
            {
1270
                KNOWN_FIELD_INDEX++,
1271
                "c-ip",
1272
                value_kind_t::VALUE_TEXT,
1273
                true,
1274
                false,
1275
                "ipaddress",
1276
            },
1277
            {
1278
                KNOWN_FIELD_INDEX++,
1279
                "cs-bytes",
1280
                value_kind_t::VALUE_INTEGER,
1281
                false,
1282
            },
1283
            {
1284
                KNOWN_FIELD_INDEX++,
1285
                "cs-host",
1286
                value_kind_t::VALUE_TEXT,
1287
                true,
1288
            },
1289
            {
1290
                KNOWN_FIELD_INDEX++,
1291
                "cs-uri-stem",
1292
                value_kind_t::VALUE_TEXT,
1293
                true,
1294
                false,
1295
                "naturalnocase",
1296
            },
1297
            {
1298
                KNOWN_FIELD_INDEX++,
1299
                "cs-uri-query",
1300
                value_kind_t::VALUE_TEXT,
1301
                false,
1302
            },
1303
            {
1304
                KNOWN_FIELD_INDEX++,
1305
                "cs-username",
1306
                value_kind_t::VALUE_TEXT,
1307
                false,
1308
            },
1309
            {
1310
                KNOWN_FIELD_INDEX++,
1311
                "cs-version",
1312
                value_kind_t::VALUE_TEXT,
1313
                true,
1314
            },
1315
            {
1316
                KNOWN_FIELD_INDEX++,
1317
                "s-ip",
1318
                value_kind_t::VALUE_TEXT,
1319
                true,
1320
                false,
1321
                "ipaddress",
1322
            },
1323
            {
1324
                KNOWN_FIELD_INDEX++,
1325
                "s-port",
1326
                value_kind_t::VALUE_INTEGER,
1327
                true,
1328
            },
1329
            {
1330
                KNOWN_FIELD_INDEX++,
1331
                "s-computername",
1332
                value_kind_t::VALUE_TEXT,
1333
                true,
1334
            },
1335
            {
1336
                KNOWN_FIELD_INDEX++,
1337
                "s-sitename",
1338
                value_kind_t::VALUE_TEXT,
1339
                true,
1340
            },
1341
            {
1342
                KNOWN_FIELD_INDEX++,
1343
                "sc-bytes",
1344
                value_kind_t::VALUE_INTEGER,
1345
                false,
1346
            },
1347
            {
1348
                KNOWN_FIELD_INDEX++,
1349
                "sc-status",
1350
                value_kind_t::VALUE_INTEGER,
1351
                false,
1352
                true,
1353
            },
1354
            {
1355
                KNOWN_FIELD_INDEX++,
1356
                "sc-substatus",
1357
                value_kind_t::VALUE_INTEGER,
1358
                false,
1359
            },
1360
            {
1361
                KNOWN_FIELD_INDEX++,
1362
                "time-taken",
1363
                value_kind_t::VALUE_FLOAT,
1364
                false,
1365
            },
1366
        };
1,780✔
1367

1368
        return RETVAL;
602✔
1369
    }
1370

1371
    static const std::array<field_to_struct_t, 4>& get_known_struct_fields()
599✔
1372
    {
1373
        static const std::array<field_to_struct_t, 4> RETVAL = {
1374
            field_to_struct_t{"cs(", "cs_headers"},
1375
            {"sc(", "sc_headers"},
1376
            {"rs(", "rs_headers"},
1377
            {"sr(", "sr_headers"},
1378
        };
599✔
1379

1380
        return RETVAL;
599✔
1381
    }
1382

1383
    w3c_log_format()
724✔
1384
    {
724✔
1385
        this->lf_is_self_describing = true;
724✔
1386
        this->lf_time_ordered = false;
724✔
1387
        this->lf_structured = true;
724✔
1388
    }
724✔
1389

1390
    const intern_string_t get_name() const override
13,175✔
1391
    {
1392
        static const intern_string_t name(intern_string::lookup("w3c_log"));
14,611✔
1393

1394
        return this->wlf_format_name.empty() ? name : this->wlf_format_name;
13,175✔
1395
    }
1396

1397
    void clear() override
13,366✔
1398
    {
1399
        this->log_format::clear();
13,366✔
1400
        this->wlf_time_scanner.clear();
13,366✔
1401
        this->wlf_format_name.clear();
13,366✔
1402
        this->wlf_field_defs.clear();
13,366✔
1403
    }
13,366✔
1404

1405
    scan_result_t scan_int(std::vector<logline>& dst,
311✔
1406
                           const line_info& li,
1407
                           shared_buffer_ref& sbr)
1408
    {
1409
        static const intern_string_t F_DATE_LOCAL
1410
            = intern_string::lookup("date-local");
337✔
1411
        static const intern_string_t F_DATE_UTC
1412
            = intern_string::lookup("date-UTC");
337✔
1413
        static const intern_string_t F_TIME_LOCAL
1414
            = intern_string::lookup("time-local");
337✔
1415
        static const intern_string_t F_TIME_UTC
1416
            = intern_string::lookup("time-UTC");
337✔
1417
        static const intern_string_t F_STATUS_CODE
1418
            = intern_string::lookup("sc-status");
337✔
1419

1420
        ws_separated_string ss(sbr.get_data(), sbr.length());
311✔
1421
        timeval date_tv{0, 0}, time_tv{0, 0};
311✔
1422
        exttm date_tm, time_tm;
311✔
1423
        bool found_date = false, found_time = false;
311✔
1424
        log_level_t level = LEVEL_INFO;
311✔
1425

1426
        for (auto iter = ss.begin(); iter != ss.end(); ++iter) {
4,341✔
1427
            if (iter.index() >= this->wlf_field_defs.size()) {
4,080✔
1428
                level = LEVEL_INVALID;
1✔
1429
                break;
1✔
1430
            }
1431

1432
            const auto& fd = this->wlf_field_defs[iter.index()];
4,079✔
1433
            string_fragment sf = *iter;
4,079✔
1434

1435
            if (sf.startswith("#")) {
4,079✔
1436
                if (sf == "#Date:") {
49✔
1437
                    auto sbr_sf_opt
1438
                        = sbr.to_string_fragment().consume_n(sf.length());
13✔
1439

1440
                    if (sbr_sf_opt) {
13✔
1441
                        auto sbr_sf = sbr_sf_opt.value().trim();
13✔
1442
                        date_time_scanner dts;
13✔
1443
                        exttm tm;
13✔
1444
                        timeval tv;
1445

1446
                        if (dts.scan(sbr_sf.data(),
13✔
1447
                                     sbr_sf.length(),
13✔
1448
                                     nullptr,
1449
                                     &tm,
1450
                                     tv))
1451
                        {
1452
                            this->lf_date_time.set_base_time(tv.tv_sec,
12✔
1453
                                                             tm.et_tm);
1454
                            this->wlf_time_scanner.set_base_time(tv.tv_sec,
12✔
1455
                                                                 tm.et_tm);
1456
                        }
1457
                    }
1458
                }
1459
                dst.emplace_back(li.li_file_range.fr_offset,
49✔
1460
                                 std::chrono::microseconds{0},
×
1461
                                 LEVEL_IGNORE,
×
1462
                                 0);
49✔
1463
                return scan_match{2000};
49✔
1464
            }
1465

1466
            sf = sf.trim("\" \t");
4,030✔
1467
            if (F_DATE == fd.fd_name || F_DATE_LOCAL == fd.fd_name
7,842✔
1468
                || F_DATE_UTC == fd.fd_name)
7,842✔
1469
            {
1470
                if (this->lf_date_time.scan(
226✔
1471
                        sf.data(), sf.length(), nullptr, &date_tm, date_tv))
226✔
1472
                {
1473
                    this->lf_timestamp_flags |= date_tm.et_flags;
225✔
1474
                    found_date = true;
225✔
1475
                }
1476
            } else if (F_TIME == fd.fd_name || F_TIME_LOCAL == fd.fd_name
7,359✔
1477
                       || F_TIME_UTC == fd.fd_name)
7,359✔
1478
            {
1479
                if (this->wlf_time_scanner.scan(
257✔
1480
                        sf.data(), sf.length(), nullptr, &time_tm, time_tv))
257✔
1481
                {
1482
                    this->lf_timestamp_flags |= time_tm.et_flags;
257✔
1483
                    found_time = true;
257✔
1484
                }
1485
            } else if (F_STATUS_CODE == fd.fd_name) {
3,547✔
1486
                if (!sf.empty() && sf[0] >= '4') {
254✔
1487
                    level = LEVEL_ERROR;
206✔
1488
                }
1489
            }
1490

1491
            if (fd.fd_numeric_index) {
4,030✔
1492
                switch (fd.fd_meta.lvm_kind) {
1,338✔
1493
                    case value_kind_t::VALUE_INTEGER:
1,338✔
1494
                    case value_kind_t::VALUE_FLOAT: {
1495
                        auto scan_float_res
1496
                            = scn::scan_value<double>(sf.to_string_view());
1,338✔
1497

1498
                        if (scan_float_res) {
1,338✔
1499
                            this->lf_value_stats[fd.fd_numeric_index.value()]
1,334✔
1500
                                .add_value(scan_float_res->value());
1,334✔
1501
                        }
1502
                        break;
1,338✔
1503
                    }
1504
                    default:
×
1505
                        break;
×
1506
                }
1507
            }
1508
        }
1509

1510
        if (found_time) {
262✔
1511
            auto tm = time_tm;
257✔
1512

1513
            if (found_date) {
257✔
1514
                tm.et_tm.tm_year = date_tm.et_tm.tm_year;
225✔
1515
                tm.et_tm.tm_mday = date_tm.et_tm.tm_mday;
225✔
1516
                tm.et_tm.tm_mon = date_tm.et_tm.tm_mon;
225✔
1517
                tm.et_tm.tm_wday = date_tm.et_tm.tm_wday;
225✔
1518
                tm.et_tm.tm_yday = date_tm.et_tm.tm_yday;
225✔
1519
            }
1520

1521
            auto tv = tm.to_timeval();
257✔
1522
            if (!this->lf_specialized) {
257✔
1523
                for (auto& ll : dst) {
50✔
1524
                    ll.set_ignore(true);
40✔
1525
                }
1526
            }
1527
            dst.emplace_back(li.li_file_range.fr_offset, tv, level, 0);
257✔
1528
            return scan_match{2000};
257✔
1529
        }
1530

1531
        return scan_no_match{"no header found"};
5✔
1532
    }
1533

1534
    scan_result_t scan(logfile& lf,
10,713✔
1535
                       std::vector<logline>& dst,
1536
                       const line_info& li,
1537
                       shared_buffer_ref& sbr,
1538
                       scan_batch_context& sbc) override
1539
    {
1540
        static const auto* W3C_LOG_NAME = intern_string::lookup("w3c_log");
11,891✔
1541
        static const auto* X_FIELDS_NAME = intern_string::lookup("x_fields");
11,891✔
1542
        static const auto& KNOWN_FIELDS = get_known_fields();
10,713✔
1543
        static const auto& KNOWN_STRUCT_FIELDS = get_known_struct_fields();
10,713✔
1544
        static auto X_FIELDS_IDX = 0;
1545

1546
        if (li.li_partial) {
10,713✔
1547
            return scan_incomplete{};
18✔
1548
        }
1549

1550
        if (dst.empty()) {
10,695✔
1551
            auto file_options = lf.get_file_options();
1,099✔
1552

1553
            if (file_options) {
1,099✔
1554
                this->lf_date_time.dts_default_zone
1555
                    = file_options->second.fo_default_zone.pp_value;
53✔
1556
            } else {
1557
                this->lf_date_time.dts_default_zone = nullptr;
1,046✔
1558
            }
1559
        }
1,099✔
1560

1561
        if (!this->wlf_format_name.empty()) {
10,695✔
1562
            return this->scan_int(dst, li, sbr);
296✔
1563
        }
1564

1565
        if (dst.empty() || dst.size() > 20 || sbr.empty()
19,699✔
1566
            || sbr.get_data()[0] == '#')
19,699✔
1567
        {
1568
            return scan_no_match{"no header found"};
7,742✔
1569
        }
1570

1571
        this->clear();
2,657✔
1572

1573
        for (auto line_iter = dst.begin(); line_iter != dst.end(); ++line_iter)
21,600✔
1574
        {
1575
            auto next_read_result = lf.read_line(line_iter);
18,943✔
1576

1577
            if (next_read_result.isErr()) {
18,943✔
1578
                return scan_no_match{"unable to read first line"};
×
1579
            }
1580

1581
            auto line = next_read_result.unwrap();
18,943✔
1582
            ws_separated_string ss(line.get_data(), line.length());
18,943✔
1583
            auto iter = ss.begin();
18,943✔
1584
            const auto directive = *iter;
18,943✔
1585

1586
            if (directive.empty() || directive[0] != '#') {
18,943✔
1587
                continue;
16,374✔
1588
            }
1589

1590
            ++iter;
2,569✔
1591
            if (iter == ss.end()) {
2,569✔
1592
                continue;
41✔
1593
            }
1594

1595
            if (directive == "#Date:") {
2,528✔
1596
                date_time_scanner dts;
8✔
1597
                struct exttm tm;
8✔
1598
                struct timeval tv;
1599

1600
                if (dts.scan(line.get_data_at(directive.length() + 1),
8✔
1601
                             line.length() - directive.length() - 1,
8✔
1602
                             nullptr,
1603
                             &tm,
1604
                             tv))
1605
                {
1606
                    this->lf_date_time.set_base_time(tv.tv_sec, tm.et_tm);
7✔
1607
                    this->wlf_time_scanner.set_base_time(tv.tv_sec, tm.et_tm);
7✔
1608
                }
1609
            } else if (directive == "#Fields:" && this->wlf_field_defs.empty())
2,520✔
1610
            {
1611
                int numeric_count = 0;
15✔
1612

1613
                do {
1614
                    auto sf = (*iter).trim(")");
142✔
1615

1616
                    auto field_iter = std::find_if(
426✔
1617
                        begin(KNOWN_FIELDS),
1618
                        end(KNOWN_FIELDS),
1619
                        [&sf](auto elem) { return sf == elem.fd_name; });
1,676✔
1620
                    if (field_iter != end(KNOWN_FIELDS)) {
284✔
1621
                        this->wlf_field_defs.emplace_back(*field_iter);
69✔
1622
                        auto& fd = this->wlf_field_defs.back();
69✔
1623
                        auto common_iter = FIELD_META.find(fd.fd_meta.lvm_name);
69✔
1624
                        if (common_iter == FIELD_META.end()) {
69✔
1625
                            auto emp_res = FIELD_META.emplace(
68✔
1626
                                fd.fd_meta.lvm_name, fd.fd_meta);
68✔
1627
                            common_iter = emp_res.first;
68✔
1628
                        }
1629
                        fd.fd_root_meta = &common_iter->second;
69✔
1630
                    } else if (sf.is_one_of("date", "time")) {
73✔
1631
                        this->wlf_field_defs.emplace_back(
28✔
1632
                            intern_string::lookup(sf));
14✔
1633
                        auto& fd = this->wlf_field_defs.back();
14✔
1634
                        auto common_iter = FIELD_META.find(fd.fd_meta.lvm_name);
14✔
1635
                        if (common_iter == FIELD_META.end()) {
14✔
1636
                            auto emp_res = FIELD_META.emplace(
13✔
1637
                                fd.fd_meta.lvm_name, fd.fd_meta);
13✔
1638
                            common_iter = emp_res.first;
13✔
1639
                        }
1640
                        fd.fd_root_meta = &common_iter->second;
14✔
1641
                    } else {
1642
                        const auto fs_iter = std::find_if(
177✔
1643
                            begin(KNOWN_STRUCT_FIELDS),
1644
                            end(KNOWN_STRUCT_FIELDS),
1645
                            [&sf](auto elem) {
197✔
1646
                                return sf.startswith(elem.fs_prefix);
197✔
1647
                            });
1648
                        if (fs_iter != end(KNOWN_STRUCT_FIELDS)) {
118✔
1649
                            const intern_string_t field_name
1650
                                = intern_string::lookup(sf.substr(3));
13✔
1651
                            this->wlf_field_defs.emplace_back(
13✔
1652
                                field_name,
1653
                                logline_value_meta(
26✔
1654
                                    field_name,
1655
                                    value_kind_t::VALUE_TEXT,
1656
                                    logline_value_meta::table_column{
×
1657
                                        KNOWN_FIELDS.size() + 1
13✔
1658
                                        + std::distance(
39✔
1659
                                            begin(KNOWN_STRUCT_FIELDS),
1660
                                            fs_iter)},
1661
                                    this)
26✔
1662
                                    .with_struct_name(fs_iter->fs_struct_name));
1663
                        } else {
1664
                            const intern_string_t field_name
1665
                                = intern_string::lookup(sf);
46✔
1666
                            this->wlf_field_defs.emplace_back(
46✔
1667
                                field_name,
1668
                                logline_value_meta(
92✔
1669
                                    field_name,
1670
                                    value_kind_t::VALUE_TEXT,
1671
                                    logline_value_meta::table_column{
×
1672
                                        KNOWN_FIELDS.size() + X_FIELDS_IDX},
92✔
1673
                                    this)
92✔
1674
                                    .with_struct_name(X_FIELDS_NAME));
1675
                        }
1676
                    }
1677
                    auto& fd = this->wlf_field_defs.back();
142✔
1678
                    fd.fd_meta.lvm_format = std::make_optional(this);
142✔
1679
                    switch (fd.fd_meta.lvm_kind) {
142✔
1680
                        case value_kind_t::VALUE_FLOAT:
27✔
1681
                        case value_kind_t::VALUE_INTEGER:
1682
                            fd.with_numeric_index(numeric_count);
27✔
1683
                            numeric_count += 1;
27✔
1684
                            break;
27✔
1685
                        default:
115✔
1686
                            break;
115✔
1687
                    }
1688

1689
                    ++iter;
142✔
1690
                } while (iter != ss.end());
142✔
1691

1692
                this->wlf_format_name = W3C_LOG_NAME;
15✔
1693
                this->lf_value_stats.resize(numeric_count);
15✔
1694
            }
1695
        }
35,358✔
1696

1697
        if (!this->wlf_format_name.empty() && !this->wlf_field_defs.empty()) {
2,657✔
1698
            return this->scan_int(dst, li, sbr);
15✔
1699
        }
1700

1701
        this->wlf_format_name.clear();
2,642✔
1702
        this->lf_value_stats.clear();
2,642✔
1703

1704
        return scan_no_match{"no header found"};
2,642✔
1705
    }
1706

1707
    void annotate(logfile* lf,
237✔
1708
                  uint64_t line_number,
1709
                  string_attrs_t& sa,
1710
                  logline_value_vector& values,
1711
                  bool annotate_module) const override
1712
    {
1713
        auto& sbr = values.lvv_sbr;
237✔
1714
        ws_separated_string ss(sbr.get_data(), sbr.length());
237✔
1715
        std::optional<line_range> date_lr;
237✔
1716
        std::optional<line_range> time_lr;
237✔
1717

1718
        for (auto iter = ss.begin(); iter != ss.end(); ++iter) {
4,062✔
1719
            auto sf = *iter;
3,825✔
1720

1721
            if (iter.index() >= this->wlf_field_defs.size()) {
3,825✔
1722
                sa.emplace_back(line_range{sf.sf_begin, -1},
×
1723
                                SA_INVALID.value("extra fields detected"));
×
1724
                return;
×
1725
            }
1726

1727
            const auto& fd = this->wlf_field_defs[iter.index()];
3,825✔
1728

1729
            if (sf == "-") {
3,825✔
1730
                sf.invalidate();
659✔
1731
            }
1732

1733
            auto lr = line_range(sf.sf_begin, sf.sf_end);
3,825✔
1734

1735
            if (lr.is_valid()) {
3,825✔
1736
                if (fd.fd_meta.lvm_name == F_DATE) {
3,166✔
1737
                    date_lr = lr;
215✔
1738
                } else if (fd.fd_meta.lvm_name == F_TIME) {
2,951✔
1739
                    time_lr = lr;
229✔
1740
                }
1741
                values.lvv_values.emplace_back(fd.fd_meta, sbr, lr);
3,166✔
1742
                if (sf.startswith("\"")) {
3,166✔
1743
                    auto& meta = values.lvv_values.back().lv_meta;
28✔
1744

1745
                    if (meta.lvm_kind == value_kind_t::VALUE_TEXT) {
28✔
1746
                        meta.lvm_kind = value_kind_t::VALUE_W3C_QUOTED;
26✔
1747
                    } else {
1748
                        meta.lvm_kind = value_kind_t::VALUE_NULL;
2✔
1749
                    }
1750
                }
1751
            } else {
1752
                values.lvv_values.emplace_back(fd.fd_meta);
659✔
1753
            }
1754
            if (fd.fd_root_meta != nullptr) {
3,825✔
1755
                values.lvv_values.back().lv_meta.lvm_user_hidden
3,128✔
1756
                    = fd.fd_root_meta->lvm_user_hidden;
3,128✔
1757
            }
1758
        }
1759
        if (time_lr) {
237✔
1760
            auto ts_lr = time_lr.value();
229✔
1761
            if (date_lr) {
229✔
1762
                if (date_lr->lr_end + 1 == time_lr->lr_start) {
214✔
1763
                    ts_lr.lr_start = date_lr->lr_start;
213✔
1764
                    ts_lr.lr_end = time_lr->lr_end;
213✔
1765
                }
1766
            }
1767

1768
            sa.emplace_back(ts_lr, L_TIMESTAMP.value());
229✔
1769
        }
1770
        log_format::annotate(lf, line_number, sa, values, annotate_module);
237✔
1771
    }
1772

1773
    const logline_value_stats* stats_for_value(
×
1774
        const intern_string_t& name) const override
1775
    {
1776
        const logline_value_stats* retval = nullptr;
×
1777

1778
        for (const auto& wlf_field_def : this->wlf_field_defs) {
×
1779
            if (wlf_field_def.fd_meta.lvm_name == name) {
×
1780
                if (!wlf_field_def.fd_numeric_index) {
×
1781
                    break;
×
1782
                }
1783
                retval = &this->lf_value_stats[wlf_field_def.fd_numeric_index
1784
                                                   .value()];
×
1785
                break;
×
1786
            }
1787
        }
1788

1789
        return retval;
×
1790
    }
1791

1792
    bool hide_field(const intern_string_t field_name, bool val) override
×
1793
    {
1794
        if (field_name == LOG_TIME_STR) {
×
1795
            auto date_iter = FIELD_META.find(F_DATE);
×
1796
            auto time_iter = FIELD_META.find(F_TIME);
×
1797
            if (date_iter == FIELD_META.end() || time_iter == FIELD_META.end())
×
1798
            {
1799
                return false;
×
1800
            }
1801
            date_iter->second.lvm_user_hidden = val;
×
1802
            time_iter->second.lvm_user_hidden = val;
×
1803
            return true;
×
1804
        }
1805

1806
        auto fd_iter = FIELD_META.find(field_name);
×
1807
        if (fd_iter == FIELD_META.end()) {
×
1808
            return false;
×
1809
        }
1810

1811
        fd_iter->second.lvm_user_hidden = val;
×
1812

1813
        return true;
×
1814
    }
1815

1816
    std::map<intern_string_t, logline_value_meta> get_field_states() override
125✔
1817
    {
1818
        std::map<intern_string_t, logline_value_meta> retval;
125✔
1819

1820
        for (const auto& fd : FIELD_META) {
125✔
1821
            retval.emplace(fd.first, fd.second);
×
1822
        }
1823

1824
        return retval;
125✔
1825
    }
×
1826

1827
    std::shared_ptr<log_format> specialized(int fmt_lock = -1) override
10✔
1828
    {
1829
        auto retval = std::make_shared<w3c_log_format>(*this);
10✔
1830

1831
        retval->lf_specialized = true;
10✔
1832
        return retval;
20✔
1833
    }
10✔
1834

1835
    class w3c_log_table : public log_format_vtab_impl {
1836
    public:
1837
        explicit w3c_log_table(const w3c_log_format& format)
7✔
1838
            : log_format_vtab_impl(format), wlt_format(format)
7✔
1839
        {
1840
        }
7✔
1841

1842
        void get_columns(std::vector<vtab_column>& cols) const override
10✔
1843
        {
1844
            for (const auto& fd : get_known_fields()) {
170✔
1845
                auto type_pair = log_vtab_impl::logline_value_to_sqlite_type(
160✔
1846
                    fd.fd_meta.lvm_kind);
160✔
1847

1848
                cols.emplace_back(fd.fd_meta.lvm_name.to_string(),
160✔
1849
                                  type_pair.first,
1850
                                  fd.fd_collator,
160✔
1851
                                  false,
320✔
1852
                                  "",
1853
                                  type_pair.second);
1854
            }
1855
            cols.emplace_back("x_fields");
10✔
1856
            cols.back().with_comment(
20✔
1857
                "A JSON-object that contains fields that are not first-class "
1858
                "columns");
1859
            for (const auto& fs : get_known_struct_fields()) {
50✔
1860
                cols.emplace_back(fs.fs_struct_name.to_string());
40✔
1861
            }
1862
        };
10✔
1863

1864
        void get_foreign_keys(
3✔
1865
            std::unordered_set<std::string>& keys_inout) const override
1866
        {
1867
            this->log_vtab_impl::get_foreign_keys(keys_inout);
3✔
1868

1869
            for (const auto& fd : get_known_fields()) {
51✔
1870
                if (fd.fd_meta.lvm_identifier || fd.fd_meta.lvm_foreign_key) {
48✔
1871
                    keys_inout.emplace(fd.fd_meta.lvm_name.to_string());
30✔
1872
                }
1873
            }
1874
        }
3✔
1875

1876
        const w3c_log_format& wlt_format;
1877
    };
1878

1879
    static std::map<intern_string_t, std::shared_ptr<w3c_log_table>>&
1880
    get_tables()
7✔
1881
    {
1882
        static std::map<intern_string_t, std::shared_ptr<w3c_log_table>> retval;
7✔
1883

1884
        return retval;
7✔
1885
    }
1886

1887
    std::shared_ptr<log_vtab_impl> get_vtab_impl() const override
611✔
1888
    {
1889
        if (this->wlf_format_name.empty()) {
611✔
1890
            return nullptr;
604✔
1891
        }
1892

1893
        std::shared_ptr<w3c_log_table> retval = nullptr;
7✔
1894

1895
        auto& tables = get_tables();
7✔
1896
        const auto iter = tables.find(this->wlf_format_name);
7✔
1897
        if (iter == tables.end()) {
7✔
1898
            retval = std::make_shared<w3c_log_table>(*this);
7✔
1899
            tables[this->wlf_format_name] = retval;
7✔
1900
        }
1901

1902
        return retval;
7✔
1903
    }
7✔
1904

1905
    void get_subline(const logline& ll,
344✔
1906
                     shared_buffer_ref& sbr,
1907
                     subline_options opts) override
1908
    {
1909
    }
344✔
1910

1911
    date_time_scanner wlf_time_scanner;
1912
    intern_string_t wlf_format_name;
1913
    std::vector<field_def> wlf_field_defs;
1914
};
1915

1916
std::unordered_map<const intern_string_t, logline_value_meta>
1917
    w3c_log_format::FIELD_META;
1918

1919
const intern_string_t w3c_log_format::F_DATE = intern_string::lookup("date");
1920
const intern_string_t w3c_log_format::F_TIME = intern_string::lookup("time");
1921

1922
struct logfmt_pair_handler {
1923
    explicit logfmt_pair_handler(date_time_scanner& dts) : lph_dt_scanner(dts)
10,709✔
1924
    {
1925
    }
10,709✔
1926

1927
    log_format::scan_result_t process_value(const string_fragment& value_frag)
3,964✔
1928
    {
1929
        if (this->lph_key_frag.is_one_of(
3,964✔
1930
                "timestamp"_frag, "time"_frag, "ts"_frag, "t"_frag))
1931
        {
1932
            if (!this->lph_dt_scanner.scan(value_frag.data(),
31✔
1933
                                           value_frag.length(),
31✔
1934
                                           nullptr,
1935
                                           &this->lph_time_tm,
1936
                                           this->lph_tv))
31✔
1937
            {
1938
                return log_format::scan_no_match{
×
1939
                    "timestamp value did not parse correctly"};
×
1940
            }
1941
            char buf[1024];
1942
            this->lph_dt_scanner.ftime(
31✔
1943
                buf, sizeof(buf), nullptr, this->lph_time_tm);
31✔
1944
            this->lph_found_time = true;
31✔
1945
        } else if (this->lph_key_frag.is_one_of("level"_frag, "lvl"_frag)) {
3,933✔
1946
            this->lph_level
1947
                = string2level(value_frag.data(), value_frag.length());
40✔
1948
        }
1949
        return log_format::scan_match{};
3,964✔
1950
    }
1951

1952
    date_time_scanner& lph_dt_scanner;
1953
    bool lph_found_time{false};
1954
    exttm lph_time_tm;
1955
    timeval lph_tv{0, 0};
1956
    log_level_t lph_level{log_level_t::LEVEL_INFO};
1957
    string_fragment lph_key_frag{""};
1958
};
1959

1960
class logfmt_format : public log_format {
1961
public:
1962
    const intern_string_t get_name() const override
13,523✔
1963
    {
1964
        const static intern_string_t NAME = intern_string::lookup("logfmt_log");
14,959✔
1965

1966
        return NAME;
13,523✔
1967
    }
1968

1969
    class logfmt_log_table : public log_format_vtab_impl {
1970
    public:
1971
        logfmt_log_table(const log_format& format)
604✔
1972
            : log_format_vtab_impl(format)
604✔
1973
        {
1974
        }
604✔
1975

1976
        void get_columns(std::vector<vtab_column>& cols) const override
605✔
1977
        {
1978
            static const auto FIELDS = std::string("fields");
1,813✔
1979

1980
            cols.emplace_back(FIELDS);
605✔
1981
        }
605✔
1982
    };
1983

1984
    std::shared_ptr<log_vtab_impl> get_vtab_impl() const override
604✔
1985
    {
1986
        static auto retval = std::make_shared<logfmt_log_table>(*this);
604✔
1987

1988
        return retval;
604✔
1989
    }
1990

1991
    scan_result_t scan(logfile& lf,
10,709✔
1992
                       std::vector<logline>& dst,
1993
                       const line_info& li,
1994
                       shared_buffer_ref& sbr,
1995
                       scan_batch_context& sbc) override
1996
    {
1997
        auto p = logfmt::parser(sbr.to_string_fragment());
10,709✔
1998
        scan_result_t retval = scan_no_match{};
10,709✔
1999
        bool done = false;
10,709✔
2000
        logfmt_pair_handler lph(this->lf_date_time);
10,709✔
2001

2002
        if (dst.empty()) {
10,709✔
2003
            auto file_options = lf.get_file_options();
1,111✔
2004

2005
            if (file_options) {
1,111✔
2006
                this->lf_date_time.dts_default_zone
2007
                    = file_options->second.fo_default_zone.pp_value;
53✔
2008
            } else {
2009
                this->lf_date_time.dts_default_zone = nullptr;
1,058✔
2010
            }
2011
        }
1,111✔
2012

2013
        while (!done) {
25,382✔
2014
            auto parse_result = p.step();
14,673✔
2015

2016
            auto value_res = parse_result.match(
2017
                [&done](const logfmt::parser::end_of_input&) -> scan_result_t {
×
2018
                    done = true;
228✔
2019
                    return scan_match{};
228✔
2020
                },
2021
                [&lph](const logfmt::parser::kvpair& kvp) -> scan_result_t {
×
2022
                    lph.lph_key_frag = kvp.first;
3,964✔
2023

2024
                    return kvp.second.match(
2025
                        [](const logfmt::parser::bool_value& bv)
×
2026
                            -> scan_result_t { return scan_match{}; },
×
2027
                        [&lph](const logfmt::parser::float_value& fv)
×
2028
                            -> scan_result_t {
2029
                            return lph.process_value(fv.fv_str_value);
5✔
2030
                        },
2031
                        [&lph](const logfmt::parser::int_value& iv)
×
2032
                            -> scan_result_t {
2033
                            return lph.process_value(iv.iv_str_value);
108✔
2034
                        },
2035
                        [&lph](const logfmt::parser::quoted_value& qv)
×
2036
                            -> scan_result_t {
2037
                            auto_mem<yajl_handle_t> handle(yajl_free);
343✔
2038
                            yajl_callbacks cb;
2039
                            scan_result_t retval;
343✔
2040

2041
                            memset(&cb, 0, sizeof(cb));
343✔
2042
                            handle = yajl_alloc(&cb, nullptr, &lph);
343✔
2043
                            cb.yajl_string = +[](void* ctx,
686✔
2044
                                                 const unsigned char* str,
2045
                                                 size_t len,
2046
                                                 yajl_string_props_t*) -> int {
2047
                                auto& lph = *((logfmt_pair_handler*) ctx);
343✔
2048
                                string_fragment value_frag{str, 0, (int) len};
343✔
2049

2050
                                auto value_res = lph.process_value(value_frag);
343✔
2051
                                return value_res.is<scan_match>();
686✔
2052
                            };
686✔
2053

2054
                            if (yajl_parse(
343✔
2055
                                    handle,
2056
                                    (const unsigned char*) qv.qv_value.data(),
343✔
2057
                                    qv.qv_value.length())
343✔
2058
                                    != yajl_status_ok
2059
                                || yajl_complete_parse(handle)
343✔
2060
                                    != yajl_status_ok)
2061
                            {
2062
                                log_debug("json parsing failed");
×
2063
                                string_fragment unq_frag{
2064
                                    qv.qv_value.sf_string,
×
2065
                                    qv.qv_value.sf_begin + 1,
×
2066
                                    qv.qv_value.sf_end - 1,
×
2067
                                };
2068

2069
                                return lph.process_value(unq_frag);
×
2070
                            }
2071

2072
                            return scan_match{};
343✔
2073
                        },
343✔
2074
                        [&lph](const logfmt::parser::unquoted_value& uv)
3,964✔
2075
                            -> scan_result_t {
2076
                            return lph.process_value(uv.uv_value);
3,508✔
2077
                        });
7,928✔
2078
                },
2079
                [](const logfmt::parser::error& err) -> scan_result_t {
×
2080
                    // log_error("logfmt parse error: %s", err.e_msg.c_str());
2081
                    return scan_no_match{};
10,481✔
2082
                });
14,673✔
2083
            if (value_res.is<scan_no_match>()) {
14,673✔
2084
                retval = value_res;
10,481✔
2085
                done = true;
10,481✔
2086
            }
2087
        }
14,673✔
2088

2089
        if (lph.lph_found_time) {
10,709✔
2090
            this->lf_timestamp_flags = lph.lph_time_tm.et_flags;
31✔
2091
            dst.emplace_back(
31✔
2092
                li.li_file_range.fr_offset, lph.lph_tv, lph.lph_level);
31✔
2093
            retval = scan_match{2000};
31✔
2094
        }
2095

2096
        return retval;
21,418✔
2097
    }
×
2098

2099
    void annotate(logfile* lf,
11✔
2100
                  uint64_t line_number,
2101
                  string_attrs_t& sa,
2102
                  logline_value_vector& values,
2103
                  bool annotate_module) const override
2104
    {
2105
        static const intern_string_t FIELDS_NAME
2106
            = intern_string::lookup("fields");
15✔
2107

2108
        auto& sbr = values.lvv_sbr;
11✔
2109
        auto p = logfmt::parser(sbr.to_string_fragment());
11✔
2110
        auto done = false;
11✔
2111
        auto found_body = false;
11✔
2112

2113
        while (!done) {
95✔
2114
            auto parse_result = p.step();
84✔
2115

2116
            done = parse_result.match(
168✔
2117
                [](const logfmt::parser::end_of_input&) { return true; },
11✔
2118
                [this, &sa, &values, &found_body](
×
2119
                    const logfmt::parser::kvpair& kvp) {
2120
                    auto value_frag = kvp.second.match(
73✔
2121
                        [this, &kvp, &values](
×
2122
                            const logfmt::parser::bool_value& bv) {
2123
                            auto lvm = logline_value_meta{intern_string::lookup(
×
2124
                                                              kvp.first),
×
2125
                                                          value_kind_t::
2126
                                                              VALUE_INTEGER,
2127
                                                          logline_value_meta::
2128
                                                              table_column{0},
×
2129
                                                          (log_format*) this}
×
2130
                                           .with_struct_name(FIELDS_NAME);
×
2131
                            values.lvv_values.emplace_back(lvm, bv.bv_value);
×
2132

2133
                            return bv.bv_str_value;
×
2134
                        },
×
2135
                        [this, &kvp, &values](
×
2136
                            const logfmt::parser::int_value& iv) {
2137
                            auto lvm = logline_value_meta{intern_string::lookup(
×
2138
                                                              kvp.first),
×
2139
                                                          value_kind_t::
2140
                                                              VALUE_INTEGER,
2141
                                                          logline_value_meta::
2142
                                                              table_column{0},
×
2143
                                                          (log_format*) this}
×
2144
                                           .with_struct_name(FIELDS_NAME);
×
2145
                            values.lvv_values.emplace_back(lvm, iv.iv_value);
×
2146

2147
                            return iv.iv_str_value;
×
2148
                        },
×
2149
                        [this, &kvp, &values](
73✔
2150
                            const logfmt::parser::float_value& fv) {
2151
                            auto lvm = logline_value_meta{intern_string::lookup(
×
2152
                                                              kvp.first),
×
2153
                                                          value_kind_t::
2154
                                                              VALUE_INTEGER,
2155
                                                          logline_value_meta::
2156
                                                              table_column{0},
×
2157
                                                          (log_format*) this}
×
2158
                                           .with_struct_name(FIELDS_NAME);
×
2159
                            values.lvv_values.emplace_back(lvm, fv.fv_value);
×
2160

2161
                            return fv.fv_str_value;
×
2162
                        },
×
2163
                        [](const logfmt::parser::quoted_value& qv) {
×
2164
                            return qv.qv_value;
24✔
2165
                        },
2166
                        [](const logfmt::parser::unquoted_value& uv) {
×
2167
                            return uv.uv_value;
49✔
2168
                        });
2169
                    auto value_lr
2170
                        = line_range{value_frag.sf_begin, value_frag.sf_end};
73✔
2171

2172
                    auto known_field = false;
73✔
2173
                    if (kvp.first.is_one_of(
73✔
2174
                            "timestamp"_frag, "time"_frag, "ts"_frag, "t"_frag))
2175
                    {
2176
                        sa.emplace_back(value_lr, L_TIMESTAMP.value());
11✔
2177
                        known_field = true;
11✔
2178
                    } else if (kvp.first.is_one_of("level"_frag, "lvl"_frag)) {
62✔
2179
                        sa.emplace_back(value_lr, L_LEVEL.value());
11✔
2180
                        known_field = true;
11✔
2181
                    } else if (kvp.first.is_one_of("msg"_frag, "message"_frag))
51✔
2182
                    {
2183
                        sa.emplace_back(value_lr, SA_BODY.value());
11✔
2184
                        found_body = true;
11✔
2185
                    } else if (kvp.second.is<logfmt::parser::quoted_value>()
40✔
2186
                               || kvp.second
78✔
2187
                                      .is<logfmt::parser::unquoted_value>())
38✔
2188
                    {
2189
                        auto lvm
2190
                            = logline_value_meta{intern_string::lookup(
160✔
2191
                                                     kvp.first),
40✔
2192
                                                 value_frag.startswith("\"")
40✔
2193
                                                     ? value_kind_t::VALUE_JSON
2194
                                                     : value_kind_t::VALUE_TEXT,
2195
                                                 logline_value_meta::
2196
                                                     table_column{0},
40✔
2197
                                                 (log_format*) this}
80✔
2198
                                  .with_struct_name(FIELDS_NAME);
40✔
2199
                        values.lvv_values.emplace_back(lvm, value_frag);
40✔
2200
                    }
40✔
2201
                    if (known_field) {
73✔
2202
                        auto key_with_eq = kvp.first;
22✔
2203
                        key_with_eq.sf_end += 1;
22✔
2204
                        sa.emplace_back(to_line_range(key_with_eq),
22✔
2205
                                        SA_REPLACED.value());
44✔
2206
                    } else {
2207
                        sa.emplace_back(to_line_range(kvp.first),
51✔
2208
                                        VC_ROLE.value(role_t::VCR_OBJECT_KEY));
102✔
2209
                    }
2210
                    return false;
73✔
2211
                },
2212
                [line_number, &sbr](const logfmt::parser::error& err) {
84✔
2213
                    log_error("bad line %.*s", sbr.length(), sbr.get_data());
×
2214
                    log_error("%lld:logfmt parse error: %s",
×
2215
                              line_number,
2216
                              err.e_msg.c_str());
2217
                    return true;
×
2218
                });
2219
        }
84✔
2220

2221
        if (!found_body) {
11✔
2222
            sa.emplace_back(line_range::empty_at(sbr.length()),
×
2223
                            SA_BODY.value());
×
2224
        }
2225

2226
        log_format::annotate(lf, line_number, sa, values, annotate_module);
11✔
2227
    }
11✔
2228

2229
    std::shared_ptr<log_format> specialized(int fmt_lock) override
5✔
2230
    {
2231
        auto retval = std::make_shared<logfmt_format>(*this);
5✔
2232

2233
        retval->lf_specialized = true;
5✔
2234
        return retval;
10✔
2235
    }
5✔
2236
};
2237

2238
static auto format_binder = injector::bind_multiple<log_format>()
2239
                                .add<logfmt_format>()
2240
                                .add<bro_log_format>()
2241
                                .add<w3c_log_format>()
2242
                                .add<generic_log_format>()
2243
                                .add<piper_log_format>();
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc