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

tstack / lnav / 19153812334-2653

07 Nov 2025 12:09AM UTC coverage: 69.033% (+0.003%) from 69.03%
19153812334-2653

push

github

tstack
[tidy] add format-string warnings

54 of 97 new or added lines in 39 files covered. (55.67%)

3 existing lines in 2 files now uncovered.

50702 of 73446 relevant lines covered (69.03%)

435988.05 hits per line

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

88.84
/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
14,623✔
55
    {
56
        static const intern_string_t RETVAL
57
            = intern_string::lookup("lnav_piper_log");
16,105✔
58

59
        return RETVAL;
14,623✔
60
    }
61

62
    scan_result_t scan(logfile& lf,
11,885✔
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()
11,885✔
69
            && lf.get_text_format() == text_format_t::TF_LOG)
11,885✔
70
        {
71
            dst.emplace_back(
293✔
72
                li.li_file_range.fr_offset, li.li_timestamp, li.li_level);
293✔
73
            return scan_match{1};
293✔
74
        }
75

76
        return scan_no_match{"not a piper capture"};
11,592✔
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()
11,934✔
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
        };
11,934✔
177

178
        return log_fmt;
11,934✔
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
14,252✔
188
    {
189
        static const intern_string_t RETVAL
190
            = intern_string::lookup("generic_log");
15,734✔
191

192
        return RETVAL;
14,252✔
193
    }
194

195
    scan_result_t scan(logfile& lf,
11,848✔
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;
11,848✔
202
        timeval log_tv;
203
        string_fragment ts;
11,848✔
204
        std::optional<string_fragment> level;
11,848✔
205
        const char* last_pos;
206

207
        if (dst.empty()) {
11,848✔
208
            auto file_options = lf.get_file_options();
199✔
209

210
            if (file_options) {
199✔
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;
197✔
215
            }
216
        }
199✔
217

218
        if ((last_pos = this->log_scanf(dst.size(),
11,848✔
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)
11,848✔
228
        {
229
            auto level_val = log_level_t::LEVEL_UNKNOWN;
2,365✔
230
            if (level) {
2,365✔
231
                level_val = string2level(level->data(), level->length());
2,365✔
232
            }
233

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

241
            if (!(this->lf_timestamp_flags
4,730✔
242
                  & (ETF_MILLIS_SET | ETF_MICROS_SET | ETF_NANOS_SET))
2,365✔
243
                && !dst.empty()
2,040✔
244
                && dst.back().get_time<std::chrono::seconds>().count()
2,038✔
245
                    == log_tv.tv_sec
2,038✔
246
                && dst.back()
5,267✔
247
                        .get_subsecond_time<std::chrono::microseconds>()
3,227✔
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 log_us = to_us(log_tv);
2,365✔
266
            auto tid_iter = sbc.sbc_tids.insert_tid(
2,365✔
267
                sbc.sbc_allocator, string_fragment{}, log_us);
×
268
            tid_iter->second.titr_level_stats.update_msg_count(level_val);
2,365✔
269
            dst.emplace_back(li.li_file_range.fr_offset, log_us, level_val);
2,365✔
270
            return scan_match{5};
2,365✔
271
        }
272

273
        return scan_no_match{"no patterns matched"};
9,483✔
274
    }
275

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

413
    return retval;
22✔
414
}
×
415

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

422
    if ((c = *find++) != '\0') {
1,577,674✔
423
        len = strlen(find);
1,577,674✔
424
        do {
425
            do {
426
                if (slen < 1 || (sc = *s) == '\0') {
6,761,318✔
427
                    return std::nullopt;
856,646✔
428
                }
429
                --slen;
5,904,672✔
430
                ++s;
5,904,672✔
431
            } while (sc != c);
5,904,672✔
432
            if (len > slen) {
721,028✔
433
                return std::nullopt;
×
434
            }
435
        } while (strncmp(s, find, len) != 0);
721,028✔
436
        s--;
721,028✔
437
    }
438
    return s;
721,028✔
439
}
440

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

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

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

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

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

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

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

492
            return *this;
754,792✔
493
        }
494

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

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

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

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

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

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

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

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

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

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

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

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

571
    static const intern_string_t get_opid_desc()
747✔
572
    {
573
        static const intern_string_t RETVAL = intern_string::lookup("std");
2,241✔
574

575
        return RETVAL;
747✔
576
    }
577

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

586
        auto desc_v = std::make_shared<std::vector<opid_descriptor>>();
747✔
587
        desc_v->emplace({});
747✔
588
        auto emplace_res = this->lf_opid_description_def->emplace(
1,494✔
589
            get_opid_desc(), opid_descriptors{desc_v, 0});
1,494✔
590
        this->lf_opid_description_def_vec->emplace_back(
747✔
591
            &emplace_res.first->second);
747✔
592
    }
747✔
593

594
    const intern_string_t get_name() const override
115,540✔
595
    {
596
        static const intern_string_t name(intern_string::lookup("bro"));
117,022✔
597

598
        return this->blf_format_name.empty() ? name : this->blf_format_name;
115,540✔
599
    }
600

601
    void clear() override
11,907✔
602
    {
603
        this->log_format::clear();
11,907✔
604
        this->blf_format_name.clear();
11,907✔
605
        this->blf_field_defs.clear();
11,907✔
606
    }
11,907✔
607

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

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

629
        ss.with_separator(this->blf_separator.get());
4,168✔
630

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

636
            if (iter.index() >= this->blf_field_defs.size()) {
118,746✔
637
                break;
×
638
            }
639

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

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

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

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

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

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

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

698
            auto log_us = to_us(tv);
4,146✔
699
            if (opid_cap.is_valid()) {
4,146✔
700
                auto opid_iter = sbc.sbc_opids.insert_op(
4,146✔
701
                    sbc.sbc_allocator,
702
                    opid_cap,
703
                    log_us,
704
                    this->lf_timestamp_point_of_reference,
705
                    duration);
706
                opid_iter->second.otr_level_stats.update_msg_count(level);
4,146✔
707

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

725
    scan_result_t scan(logfile& lf,
11,885✔
726
                       std::vector<logline>& dst,
727
                       const line_info& li,
728
                       shared_buffer_ref& sbr,
729
                       scan_batch_context& sbc) override
730
    {
731
        static const auto SEP_RE
732
            = lnav::pcre2pp::code::from_const(R"(^#separator\s+(.+))");
11,885✔
733

734
        if (dst.empty()) {
11,885✔
735
            auto file_options = lf.get_file_options();
1,138✔
736

737
            if (file_options) {
1,138✔
738
                this->lf_date_time.dts_default_zone
739
                    = file_options->second.fo_default_zone.pp_value;
57✔
740
            } else {
741
                this->lf_date_time.dts_default_zone = nullptr;
1,081✔
742
            }
743
        }
1,138✔
744

745
        if (!this->blf_format_name.empty()) {
11,885✔
746
            return this->scan_int(dst, li, sbr, sbc);
4,146✔
747
        }
748

749
        if (dst.empty() || dst.size() > 20 || sbr.empty()
14,340✔
750
            || sbr.get_data()[0] == '#')
14,340✔
751
        {
752
            return scan_no_match{"no header found"};
5,172✔
753
        }
754

755
        auto line_iter = dst.begin();
2,567✔
756
        auto read_result = lf.read_line(line_iter);
2,567✔
757

758
        if (read_result.isErr()) {
2,567✔
759
            return scan_no_match{"unable to read first line"};
×
760
        }
761

762
        auto line = read_result.unwrap();
2,567✔
763
        auto md = SEP_RE.create_match_data();
2,567✔
764

765
        auto match_res = SEP_RE.capture_from(line.to_string_fragment())
2,567✔
766
                             .into(md)
2,567✔
767
                             .matches(PCRE2_NO_UTF_CHECK)
5,134✔
768
                             .ignore_error();
2,567✔
769
        if (!match_res) {
2,567✔
770
            return scan_no_match{"cannot read separator header"};
2,545✔
771
        }
772

773
        this->clear();
22✔
774

775
        auto sep = from_escaped_string(md[1]->data(), md[1]->length());
22✔
776
        this->blf_separator = intern_string::lookup(sep);
22✔
777

778
        for (++line_iter; line_iter != dst.end(); ++line_iter) {
176✔
779
            auto next_read_result = lf.read_line(line_iter);
154✔
780

781
            if (next_read_result.isErr()) {
154✔
782
                return scan_no_match{"unable to read header line"};
×
783
            }
784

785
            line = next_read_result.unwrap();
154✔
786
            separated_string ss(line.get_data(), line.length());
154✔
787

788
            ss.with_separator(this->blf_separator.get());
154✔
789
            auto iter = ss.begin();
154✔
790

791
            string_fragment directive = *iter;
154✔
792

793
            if (directive.empty() || directive[0] != '#') {
154✔
794
                continue;
×
795
            }
796

797
            ++iter;
154✔
798
            if (iter == ss.end()) {
154✔
799
                continue;
×
800
            }
801

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

850
                int numeric_count = 0;
22✔
851

852
                do {
853
                    string_fragment field_type = *iter;
622✔
854
                    auto& fd = this->blf_field_defs[iter.index() - 1];
622✔
855

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

888
                    ++iter;
622✔
889
                } while (iter != ss.end());
622✔
890

891
                this->lf_value_stats.resize(numeric_count);
22✔
892
            }
893
        }
154✔
894

895
        if (!this->blf_format_name.empty() && !this->blf_separator.empty()
44✔
896
            && !this->blf_field_defs.empty())
44✔
897
        {
898
            return this->scan_int(dst, li, sbr, sbc);
22✔
899
        }
900

901
        this->blf_format_name.clear();
×
902
        this->lf_value_stats.clear();
×
903

904
        return scan_no_match{"no header found"};
×
905
    }
2,567✔
906

907
    void annotate(logfile* lf,
29,800✔
908
                  uint64_t line_number,
909
                  string_attrs_t& sa,
910
                  logline_value_vector& values,
911
                  bool annotate_module) const override
912
    {
913
        static const intern_string_t UID = intern_string::lookup("bro_uid");
29,838✔
914

915
        auto& sbr = values.lvv_sbr;
29,800✔
916
        separated_string ss(sbr.get_data(), sbr.length());
29,800✔
917

918
        ss.with_separator(this->blf_separator.get());
29,800✔
919

920
        for (auto iter = ss.begin(); iter != ss.end(); ++iter) {
664,448✔
921
            if (iter.index() >= this->blf_field_defs.size()) {
634,852✔
922
                return;
204✔
923
            }
924

925
            const field_def& fd = this->blf_field_defs[iter.index()];
634,648✔
926
            string_fragment sf = *iter;
634,648✔
927

928
            if (sf == this->blf_empty_field) {
634,648✔
929
                sf.clear();
29,603✔
930
            } else if (sf == this->blf_unset_field) {
605,045✔
931
                sf.invalidate();
69,070✔
932
            }
933

934
            auto lr = line_range(sf.sf_begin, sf.sf_end);
634,648✔
935

936
            if (fd.fd_meta.lvm_name == TS) {
634,648✔
937
                sa.emplace_back(lr, L_TIMESTAMP.value());
29,800✔
938
            } else if (fd.fd_meta.lvm_name == UID) {
604,848✔
939
                sa.emplace_back(lr, L_OPID.value());
29,800✔
940
                values.lvv_opid_value = sf.to_string();
29,800✔
941
                values.lvv_opid_provenance
942
                    = logline_value_vector::opid_provenance::file;
29,800✔
943
            }
944

945
            if (lr.is_valid()) {
634,648✔
946
                values.lvv_values.emplace_back(fd.fd_meta, sbr, lr);
565,578✔
947
            } else {
948
                values.lvv_values.emplace_back(fd.fd_meta);
69,070✔
949
            }
950
            values.lvv_values.back().lv_meta.lvm_user_hidden
634,648✔
951
                = fd.fd_root_meta->lvm_user_hidden;
634,648✔
952
        }
953

954
        log_format::annotate(lf, line_number, sa, values, annotate_module);
29,596✔
955
    }
956

957
    const logline_value_stats* stats_for_value(
35✔
958
        const intern_string_t& name) const override
959
    {
960
        const logline_value_stats* retval = nullptr;
35✔
961

962
        for (const auto& blf_field_def : this->blf_field_defs) {
525✔
963
            if (blf_field_def.fd_meta.lvm_name == name) {
525✔
964
                if (!blf_field_def.fd_numeric_index) {
35✔
965
                    break;
×
966
                }
967
                retval = &this->lf_value_stats[blf_field_def.fd_numeric_index
968
                                                   .value()];
35✔
969
                break;
35✔
970
            }
971
        }
972

973
        return retval;
35✔
974
    }
975

976
    bool hide_field(intern_string_t field_name, bool val) override
2✔
977
    {
978
        if (field_name == LOG_TIME_STR) {
2✔
979
            field_name = TS;
×
980
        }
981

982
        auto fd_iter = FIELD_META.find(field_name);
2✔
983
        if (fd_iter == FIELD_META.end()) {
2✔
984
            return false;
×
985
        }
986

987
        fd_iter->second.lvm_user_hidden = val;
2✔
988

989
        return true;
2✔
990
    }
991

992
    std::map<intern_string_t, logline_value_meta> get_field_states() override
52✔
993
    {
994
        std::map<intern_string_t, logline_value_meta> retval;
52✔
995

996
        for (const auto& fd : FIELD_META) {
168✔
997
            retval.emplace(fd.first, fd.second);
116✔
998
        }
999

1000
        return retval;
52✔
1001
    }
×
1002

1003
    std::shared_ptr<log_format> specialized(int fmt_lock = -1) override
22✔
1004
    {
1005
        auto retval = std::make_shared<bro_log_format>(*this);
22✔
1006

1007
        retval->lf_specialized = true;
22✔
1008
        return retval;
44✔
1009
    }
22✔
1010

1011
    class bro_log_table : public log_format_vtab_impl {
1012
    public:
1013
        explicit bro_log_table(const bro_log_format& format)
20✔
1014
            : log_format_vtab_impl(format), blt_format(format)
20✔
1015
        {
1016
        }
20✔
1017

1018
        void get_columns(std::vector<vtab_column>& cols) const override
29✔
1019
        {
1020
            for (const auto& fd : this->blt_format.blf_field_defs) {
854✔
1021
                auto type_pair = log_vtab_impl::logline_value_to_sqlite_type(
825✔
1022
                    fd.fd_meta.lvm_kind);
825✔
1023

1024
                cols.emplace_back(fd.fd_meta.lvm_name.to_string(),
825✔
1025
                                  type_pair.first,
1026
                                  fd.fd_collator,
825✔
1027
                                  false,
1,650✔
1028
                                  "",
1029
                                  type_pair.second);
1030
            }
1031
        }
29✔
1032

1033
        void get_foreign_keys(
10✔
1034
            std::unordered_set<std::string>& keys_inout) const override
1035
        {
1036
            this->log_vtab_impl::get_foreign_keys(keys_inout);
10✔
1037

1038
            for (const auto& fd : this->blt_format.blf_field_defs) {
292✔
1039
                if (fd.fd_meta.lvm_identifier || fd.fd_meta.lvm_foreign_key) {
282✔
1040
                    keys_inout.emplace(fd.fd_meta.lvm_name.to_string());
123✔
1041
                }
1042
            }
1043
        }
10✔
1044

1045
        const bro_log_format& blt_format;
1046
    };
1047

1048
    static std::map<intern_string_t, std::shared_ptr<bro_log_table>>&
1049
    get_tables()
20✔
1050
    {
1051
        static std::map<intern_string_t, std::shared_ptr<bro_log_table>> retval;
20✔
1052

1053
        return retval;
20✔
1054
    }
1055

1056
    std::shared_ptr<log_vtab_impl> get_vtab_impl() const override
647✔
1057
    {
1058
        if (this->blf_format_name.empty()) {
647✔
1059
            return nullptr;
627✔
1060
        }
1061

1062
        std::shared_ptr<bro_log_table> retval = nullptr;
20✔
1063

1064
        auto& tables = get_tables();
20✔
1065
        const auto iter = tables.find(this->blf_format_name);
20✔
1066
        if (iter == tables.end()) {
20✔
1067
            retval = std::make_shared<bro_log_table>(*this);
20✔
1068
            tables[this->blf_format_name] = retval;
20✔
1069
        }
1070

1071
        return retval;
20✔
1072
    }
20✔
1073

1074
    void get_subline(const logline& ll,
33,808✔
1075
                     shared_buffer_ref& sbr,
1076
                     subline_options opts) override
1077
    {
1078
    }
33,808✔
1079

1080
    intern_string_t blf_format_name;
1081
    intern_string_t blf_separator;
1082
    intern_string_t blf_set_separator;
1083
    intern_string_t blf_empty_field;
1084
    intern_string_t blf_unset_field;
1085
    std::vector<field_def> blf_field_defs;
1086
};
1087

1088
std::unordered_map<const intern_string_t, logline_value_meta>
1089
    bro_log_format::FIELD_META;
1090

1091
const intern_string_t bro_log_format::TS = intern_string::lookup("bro_ts");
1092
const intern_string_t bro_log_format::DURATION
1093
    = intern_string::lookup("bro_duration");
1094

1095
struct ws_separated_string {
1096
    const char* ss_str;
1097
    size_t ss_len;
1098

1099
    explicit ws_separated_string(const char* str = nullptr, size_t len = -1)
22,136✔
1100
        : ss_str(str), ss_len(len)
22,136✔
1101
    {
1102
    }
22,136✔
1103

1104
    struct iterator {
1105
        enum class state_t {
1106
            NORMAL,
1107
            QUOTED,
1108
        };
1109

1110
        const ws_separated_string& i_parent;
1111
        const char* i_pos;
1112
        const char* i_next_pos;
1113
        size_t i_index{0};
1114
        state_t i_state{state_t::NORMAL};
1115

1116
        iterator(const ws_separated_string& ss, const char* pos)
70,786✔
1117
            : i_parent(ss), i_pos(pos), i_next_pos(pos)
70,786✔
1118
        {
1119
            this->update();
70,786✔
1120
        }
70,786✔
1121

1122
        void update()
116,655✔
1123
        {
1124
            const auto& ss = this->i_parent;
116,655✔
1125
            bool done = false;
116,655✔
1126

1127
            while (!done && this->i_next_pos < (ss.ss_str + ss.ss_len)) {
852,691✔
1128
                switch (this->i_state) {
736,036✔
1129
                    case state_t::NORMAL:
729,278✔
1130
                        if (*this->i_next_pos == '"') {
729,278✔
1131
                            this->i_state = state_t::QUOTED;
255✔
1132
                        } else if (isspace(*this->i_next_pos)) {
729,023✔
1133
                            done = true;
59,523✔
1134
                        }
1135
                        break;
729,278✔
1136
                    case state_t::QUOTED:
6,758✔
1137
                        if (*this->i_next_pos == '"') {
6,758✔
1138
                            this->i_state = state_t::NORMAL;
255✔
1139
                        }
1140
                        break;
6,758✔
1141
                }
1142
                if (!done) {
736,036✔
1143
                    this->i_next_pos += 1;
676,513✔
1144
                }
1145
            }
1146
        }
116,655✔
1147

1148
        iterator& operator++()
45,869✔
1149
        {
1150
            const auto& ss = this->i_parent;
45,869✔
1151

1152
            this->i_pos = this->i_next_pos;
45,869✔
1153
            while (this->i_pos < (ss.ss_str + ss.ss_len)
45,869✔
1154
                   && isspace(*this->i_pos))
89,119✔
1155
            {
1156
                this->i_pos += 1;
43,250✔
1157
                this->i_next_pos += 1;
43,250✔
1158
            }
1159
            this->update();
45,869✔
1160
            this->i_index += 1;
45,869✔
1161

1162
            return *this;
45,869✔
1163
        }
1164

1165
        string_fragment operator*()
62,848✔
1166
        {
1167
            const auto& ss = this->i_parent;
62,848✔
1168
            int end = this->i_next_pos - ss.ss_str;
62,848✔
1169

1170
            return string_fragment(ss.ss_str, this->i_pos - ss.ss_str, end);
62,848✔
1171
        }
1172

1173
        bool operator==(const iterator& other) const
48,650✔
1174
        {
1175
            return (&this->i_parent == &other.i_parent)
48,650✔
1176
                && (this->i_pos == other.i_pos);
48,650✔
1177
        }
1178

1179
        bool operator!=(const iterator& other) const
46,065✔
1180
        {
1181
            return !(*this == other);
46,065✔
1182
        }
1183

1184
        size_t index() const { return this->i_index; }
86,567✔
1185
    };
1186

1187
    iterator begin() { return {*this, this->ss_str}; }
22,136✔
1188

1189
    iterator end() { return {*this, this->ss_str + this->ss_len}; }
48,650✔
1190
};
1191

1192
class w3c_log_format : public log_format {
1193
public:
1194
    static const intern_string_t F_DATE;
1195
    static const intern_string_t F_TIME;
1196

1197
    struct field_def {
1198
        const intern_string_t fd_name;
1199
        logline_value_meta fd_meta;
1200
        logline_value_meta* fd_root_meta{nullptr};
1201
        std::string fd_collator;
1202
        std::optional<size_t> fd_numeric_index;
1203

1204
        explicit field_def(const intern_string_t name)
22✔
1205
            : fd_name(name), fd_meta(intern_string::lookup(sql_safe_ident(
44✔
1206
                                         name.to_string_fragment())),
44✔
1207
                                     value_kind_t::VALUE_TEXT)
22✔
1208
        {
1209
        }
22✔
1210

1211
        field_def(const intern_string_t name, logline_value_meta meta)
71✔
1212
            : fd_name(name), fd_meta(meta)
71✔
1213
        {
1214
        }
71✔
1215

1216
        field_def(size_t col,
9,760✔
1217
                  const char* name,
1218
                  value_kind_t kind,
1219
                  bool ident = false,
1220
                  bool foreign_key = false,
1221
                  std::string coll = "")
1222
            : fd_name(intern_string::lookup(name)),
19,520✔
1223
              fd_meta(
19,520✔
1224
                  intern_string::lookup(sql_safe_ident(string_fragment(name))),
19,520✔
1225
                  kind,
1226
                  logline_value_meta::table_column{col}),
9,760✔
1227
              fd_collator(std::move(coll))
9,760✔
1228
        {
1229
            this->fd_meta.lvm_identifier = ident;
9,760✔
1230
            this->fd_meta.lvm_foreign_key = foreign_key;
9,760✔
1231
        }
9,760✔
1232

1233
        field_def& with_kind(value_kind_t kind,
1234
                             bool identifier = false,
1235
                             const std::string& collator = "")
1236
        {
1237
            this->fd_meta.lvm_kind = kind;
1238
            this->fd_meta.lvm_identifier = identifier;
1239
            this->fd_collator = collator;
1240
            return *this;
1241
        }
1242

1243
        field_def& with_numeric_index(int index)
51✔
1244
        {
1245
            this->fd_numeric_index = index;
51✔
1246
            return *this;
51✔
1247
        }
1248
    };
1249

1250
    static std::unordered_map<const intern_string_t, logline_value_meta>
1251
        FIELD_META;
1252

1253
    struct field_to_struct_t {
1254
        field_to_struct_t(const char* prefix, const char* struct_name)
2,440✔
1255
            : fs_prefix(prefix),
2,440✔
1256
              fs_struct_name(intern_string::lookup(struct_name))
4,880✔
1257
        {
1258
        }
2,440✔
1259

1260
        const char* fs_prefix;
1261
        intern_string_t fs_struct_name;
1262
    };
1263

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

1373
        return RETVAL;
627✔
1374
    }
1375

1376
    static const std::array<field_to_struct_t, 4>& get_known_struct_fields()
624✔
1377
    {
1378
        static const std::array<field_to_struct_t, 4> RETVAL = {
1379
            field_to_struct_t{"cs(", "cs_headers"},
1380
            {"sc(", "sc_headers"},
1381
            {"rs(", "rs_headers"},
1382
            {"sr(", "sr_headers"},
1383
        };
624✔
1384

1385
        return RETVAL;
624✔
1386
    }
1387

1388
    w3c_log_format()
747✔
1389
    {
747✔
1390
        this->lf_is_self_describing = true;
747✔
1391
        this->lf_time_ordered = false;
747✔
1392
        this->lf_structured = true;
747✔
1393
    }
747✔
1394

1395
    const intern_string_t get_name() const override
15,043✔
1396
    {
1397
        static const intern_string_t name(intern_string::lookup("w3c_log"));
16,525✔
1398

1399
        return this->wlf_format_name.empty() ? name : this->wlf_format_name;
15,043✔
1400
    }
1401

1402
    void clear() override
14,622✔
1403
    {
1404
        this->log_format::clear();
14,622✔
1405
        this->wlf_time_scanner.clear();
14,622✔
1406
        this->wlf_format_name.clear();
14,622✔
1407
        this->wlf_field_defs.clear();
14,622✔
1408
    }
14,622✔
1409

1410
    scan_result_t scan_int(std::vector<logline>& dst,
1,315✔
1411
                           const line_info& li,
1412
                           shared_buffer_ref& sbr)
1413
    {
1414
        static const intern_string_t F_DATE_LOCAL
1415
            = intern_string::lookup("date-local");
1,349✔
1416
        static const intern_string_t F_DATE_UTC
1417
            = intern_string::lookup("date-UTC");
1,349✔
1418
        static const intern_string_t F_TIME_LOCAL
1419
            = intern_string::lookup("time-local");
1,349✔
1420
        static const intern_string_t F_TIME_UTC
1421
            = intern_string::lookup("time-UTC");
1,349✔
1422
        static const intern_string_t F_STATUS_CODE
1423
            = intern_string::lookup("sc-status");
1,349✔
1424

1425
        ws_separated_string ss(sbr.get_data(), sbr.length());
1,315✔
1426
        timeval date_tv{0, 0}, time_tv{0, 0};
1,315✔
1427
        exttm date_tm, time_tm;
1,315✔
1428
        bool found_date = false, found_time = false;
1,315✔
1429
        log_level_t level = LEVEL_INFO;
1,315✔
1430

1431
        for (auto iter = ss.begin(); iter != ss.end(); ++iter) {
19,693✔
1432
            if (iter.index() >= this->wlf_field_defs.size()) {
18,588✔
1433
                level = LEVEL_INVALID;
1✔
1434
                break;
1✔
1435
            }
1436

1437
            const auto& fd = this->wlf_field_defs[iter.index()];
18,587✔
1438
            string_fragment sf = *iter;
18,587✔
1439

1440
            if (sf.startswith("#")) {
18,587✔
1441
                if (sf == "#Date:") {
209✔
1442
                    auto sbr_sf_opt
1443
                        = sbr.to_string_fragment().consume_n(sf.length());
53✔
1444

1445
                    if (sbr_sf_opt) {
53✔
1446
                        auto sbr_sf = sbr_sf_opt.value().trim();
53✔
1447
                        date_time_scanner dts;
53✔
1448
                        exttm tm;
53✔
1449
                        timeval tv;
1450

1451
                        if (dts.scan(sbr_sf.data(),
53✔
1452
                                     sbr_sf.length(),
53✔
1453
                                     nullptr,
1454
                                     &tm,
1455
                                     tv))
1456
                        {
1457
                            this->lf_date_time.set_base_time(tv.tv_sec,
52✔
1458
                                                             tm.et_tm);
1459
                            this->wlf_time_scanner.set_base_time(tv.tv_sec,
52✔
1460
                                                                 tm.et_tm);
1461
                        }
1462
                    }
1463
                }
1464
                dst.emplace_back(li.li_file_range.fr_offset,
209✔
1465
                                 std::chrono::microseconds{0},
×
1466
                                 LEVEL_IGNORE,
×
1467
                                 0);
209✔
1468
                return scan_match{2000};
209✔
1469
            }
1470

1471
            sf = sf.trim("\" \t");
18,378✔
1472
            if (F_DATE == fd.fd_name || F_DATE_LOCAL == fd.fd_name
35,694✔
1473
                || F_DATE_UTC == fd.fd_name)
35,694✔
1474
            {
1475
                if (this->lf_date_time.scan(
1,070✔
1476
                        sf.data(), sf.length(), nullptr, &date_tm, date_tv))
1,070✔
1477
                {
1478
                    this->lf_timestamp_flags |= date_tm.et_flags;
1,069✔
1479
                    found_date = true;
1,069✔
1480
                }
1481
            } else if (F_TIME == fd.fd_name || F_TIME_LOCAL == fd.fd_name
33,523✔
1482
                       || F_TIME_UTC == fd.fd_name)
33,523✔
1483
            {
1484
                if (this->wlf_time_scanner.scan(
1,101✔
1485
                        sf.data(), sf.length(), nullptr, &time_tm, time_tv))
1,101✔
1486
                {
1487
                    this->lf_timestamp_flags |= time_tm.et_flags;
1,101✔
1488
                    found_time = true;
1,101✔
1489
                }
1490
            } else if (F_STATUS_CODE == fd.fd_name) {
16,207✔
1491
                if (!sf.empty() && sf[0] >= '4') {
1,098✔
1492
                    level = LEVEL_ERROR;
1,018✔
1493
                }
1494
            }
1495

1496
            if (fd.fd_numeric_index) {
18,378✔
1497
                switch (fd.fd_meta.lvm_kind) {
6,402✔
1498
                    case value_kind_t::VALUE_INTEGER:
6,402✔
1499
                    case value_kind_t::VALUE_FLOAT: {
1500
                        auto scan_float_res
1501
                            = scn::scan_value<double>(sf.to_string_view());
6,402✔
1502

1503
                        if (scan_float_res) {
6,402✔
1504
                            this->lf_value_stats[fd.fd_numeric_index.value()]
6,398✔
1505
                                .add_value(scan_float_res->value());
6,398✔
1506
                        }
1507
                        break;
6,402✔
1508
                    }
1509
                    default:
×
1510
                        break;
×
1511
                }
1512
            }
1513
        }
1514

1515
        if (found_time) {
1,106✔
1516
            auto tm = time_tm;
1,101✔
1517

1518
            if (found_date) {
1,101✔
1519
                tm.et_tm.tm_year = date_tm.et_tm.tm_year;
1,069✔
1520
                tm.et_tm.tm_mday = date_tm.et_tm.tm_mday;
1,069✔
1521
                tm.et_tm.tm_mon = date_tm.et_tm.tm_mon;
1,069✔
1522
                tm.et_tm.tm_wday = date_tm.et_tm.tm_wday;
1,069✔
1523
                tm.et_tm.tm_yday = date_tm.et_tm.tm_yday;
1,069✔
1524
            }
1525

1526
            auto tv = tm.to_timeval();
1,101✔
1527
            if (!this->lf_specialized) {
1,101✔
1528
                for (auto& ll : dst) {
70✔
1529
                    ll.set_ignore(true);
56✔
1530
                }
1531
            }
1532
            dst.emplace_back(li.li_file_range.fr_offset, tv, level, 0);
1,101✔
1533
            return scan_match{2000};
1,101✔
1534
        }
1535

1536
        return scan_no_match{"no header found"};
5✔
1537
    }
1538

1539
    scan_result_t scan(logfile& lf,
11,905✔
1540
                       std::vector<logline>& dst,
1541
                       const line_info& li,
1542
                       shared_buffer_ref& sbr,
1543
                       scan_batch_context& sbc) override
1544
    {
1545
        static const auto* W3C_LOG_NAME = intern_string::lookup("w3c_log");
13,125✔
1546
        static const auto* X_FIELDS_NAME = intern_string::lookup("x_fields");
13,125✔
1547
        static const auto& KNOWN_FIELDS = get_known_fields();
11,905✔
1548
        static const auto& KNOWN_STRUCT_FIELDS = get_known_struct_fields();
11,905✔
1549
        static auto X_FIELDS_IDX = 0;
1550

1551
        if (li.li_partial) {
11,905✔
1552
            return scan_incomplete{};
19✔
1553
        }
1554

1555
        if (dst.empty()) {
11,886✔
1556
            auto file_options = lf.get_file_options();
1,136✔
1557

1558
            if (file_options) {
1,136✔
1559
                this->lf_date_time.dts_default_zone
1560
                    = file_options->second.fo_default_zone.pp_value;
57✔
1561
            } else {
1562
                this->lf_date_time.dts_default_zone = nullptr;
1,079✔
1563
            }
1564
        }
1,136✔
1565

1566
        if (!this->wlf_format_name.empty()) {
11,886✔
1567
            return this->scan_int(dst, li, sbr);
1,296✔
1568
        }
1569

1570
        if (dst.empty() || dst.size() > 20 || sbr.empty()
20,044✔
1571
            || sbr.get_data()[0] == '#')
20,044✔
1572
        {
1573
            return scan_no_match{"no header found"};
7,853✔
1574
        }
1575

1576
        this->clear();
2,737✔
1577

1578
        for (auto line_iter = dst.begin(); line_iter != dst.end(); ++line_iter)
22,092✔
1579
        {
1580
            auto next_read_result = lf.read_line(line_iter);
19,355✔
1581

1582
            if (next_read_result.isErr()) {
19,355✔
1583
                return scan_no_match{"unable to read first line"};
×
1584
            }
1585

1586
            auto line = next_read_result.unwrap();
19,355✔
1587
            ws_separated_string ss(line.get_data(), line.length());
19,355✔
1588
            auto iter = ss.begin();
19,355✔
1589
            const auto directive = *iter;
19,355✔
1590

1591
            if (directive.empty() || directive[0] != '#') {
19,355✔
1592
                continue;
16,770✔
1593
            }
1594

1595
            ++iter;
2,585✔
1596
            if (iter == ss.end()) {
2,585✔
1597
                continue;
41✔
1598
            }
1599

1600
            if (directive == "#Date:") {
2,544✔
1601
                date_time_scanner dts;
12✔
1602
                struct exttm tm;
12✔
1603
                struct timeval tv;
1604

1605
                if (dts.scan(line.get_data_at(directive.length() + 1),
12✔
1606
                             line.length() - directive.length() - 1,
12✔
1607
                             nullptr,
1608
                             &tm,
1609
                             tv))
1610
                {
1611
                    this->lf_date_time.set_base_time(tv.tv_sec, tm.et_tm);
11✔
1612
                    this->wlf_time_scanner.set_base_time(tv.tv_sec, tm.et_tm);
11✔
1613
                }
1614
            } else if (directive == "#Fields:" && this->wlf_field_defs.empty())
2,532✔
1615
            {
1616
                int numeric_count = 0;
19✔
1617

1618
                do {
1619
                    auto sf = (*iter).trim(")");
210✔
1620

1621
                    auto field_iter = std::find_if(
630✔
1622
                        begin(KNOWN_FIELDS),
1623
                        end(KNOWN_FIELDS),
1624
                        [&sf](auto elem) { return sf == elem.fd_name; });
2,400✔
1625
                    if (field_iter != end(KNOWN_FIELDS)) {
420✔
1626
                        this->wlf_field_defs.emplace_back(*field_iter);
117✔
1627
                        auto& fd = this->wlf_field_defs.back();
117✔
1628
                        auto common_iter = FIELD_META.find(fd.fd_meta.lvm_name);
117✔
1629
                        if (common_iter == FIELD_META.end()) {
117✔
1630
                            auto emp_res = FIELD_META.emplace(
116✔
1631
                                fd.fd_meta.lvm_name, fd.fd_meta);
116✔
1632
                            common_iter = emp_res.first;
116✔
1633
                        }
1634
                        fd.fd_root_meta = &common_iter->second;
117✔
1635
                    } else if (sf.is_one_of("date", "time")) {
93✔
1636
                        this->wlf_field_defs.emplace_back(
44✔
1637
                            intern_string::lookup(sf));
22✔
1638
                        auto& fd = this->wlf_field_defs.back();
22✔
1639
                        auto common_iter = FIELD_META.find(fd.fd_meta.lvm_name);
22✔
1640
                        if (common_iter == FIELD_META.end()) {
22✔
1641
                            auto emp_res = FIELD_META.emplace(
21✔
1642
                                fd.fd_meta.lvm_name, fd.fd_meta);
21✔
1643
                            common_iter = emp_res.first;
21✔
1644
                        }
1645
                        fd.fd_root_meta = &common_iter->second;
22✔
1646
                    } else {
1647
                        const auto fs_iter = std::find_if(
213✔
1648
                            begin(KNOWN_STRUCT_FIELDS),
1649
                            end(KNOWN_STRUCT_FIELDS),
1650
                            [&sf](auto elem) {
221✔
1651
                                return sf.startswith(elem.fs_prefix);
221✔
1652
                            });
1653
                        if (fs_iter != end(KNOWN_STRUCT_FIELDS)) {
142✔
1654
                            const intern_string_t field_name
1655
                                = intern_string::lookup(sf.substr(3));
21✔
1656
                            this->wlf_field_defs.emplace_back(
21✔
1657
                                field_name,
1658
                                logline_value_meta(
42✔
1659
                                    field_name,
1660
                                    value_kind_t::VALUE_TEXT,
1661
                                    logline_value_meta::table_column{
×
1662
                                        KNOWN_FIELDS.size() + 1
21✔
1663
                                        + std::distance(
63✔
1664
                                            begin(KNOWN_STRUCT_FIELDS),
1665
                                            fs_iter)},
1666
                                    this)
42✔
1667
                                    .with_struct_name(fs_iter->fs_struct_name));
1668
                        } else {
1669
                            const intern_string_t field_name
1670
                                = intern_string::lookup(sf);
50✔
1671
                            this->wlf_field_defs.emplace_back(
50✔
1672
                                field_name,
1673
                                logline_value_meta(
100✔
1674
                                    field_name,
1675
                                    value_kind_t::VALUE_TEXT,
1676
                                    logline_value_meta::table_column{
×
1677
                                        KNOWN_FIELDS.size() + X_FIELDS_IDX},
100✔
1678
                                    this)
100✔
1679
                                    .with_struct_name(X_FIELDS_NAME));
1680
                        }
1681
                    }
1682
                    auto& fd = this->wlf_field_defs.back();
210✔
1683
                    fd.fd_meta.lvm_format = std::make_optional(this);
210✔
1684
                    switch (fd.fd_meta.lvm_kind) {
210✔
1685
                        case value_kind_t::VALUE_FLOAT:
51✔
1686
                        case value_kind_t::VALUE_INTEGER:
1687
                            fd.with_numeric_index(numeric_count);
51✔
1688
                            numeric_count += 1;
51✔
1689
                            break;
51✔
1690
                        default:
159✔
1691
                            break;
159✔
1692
                    }
1693

1694
                    ++iter;
210✔
1695
                } while (iter != ss.end());
210✔
1696

1697
                this->wlf_format_name = W3C_LOG_NAME;
19✔
1698
                this->lf_value_stats.resize(numeric_count);
19✔
1699
            }
1700
        }
36,166✔
1701

1702
        if (!this->wlf_format_name.empty() && !this->wlf_field_defs.empty()) {
2,737✔
1703
            return this->scan_int(dst, li, sbr);
19✔
1704
        }
1705

1706
        this->wlf_format_name.clear();
2,718✔
1707
        this->lf_value_stats.clear();
2,718✔
1708

1709
        return scan_no_match{"no header found"};
2,718✔
1710
    }
1711

1712
    void annotate(logfile* lf,
1,466✔
1713
                  uint64_t line_number,
1714
                  string_attrs_t& sa,
1715
                  logline_value_vector& values,
1716
                  bool annotate_module) const override
1717
    {
1718
        auto& sbr = values.lvv_sbr;
1,466✔
1719
        ws_separated_string ss(sbr.get_data(), sbr.length());
1,466✔
1720
        std::optional<line_range> date_lr;
1,466✔
1721
        std::optional<line_range> time_lr;
1,466✔
1722

1723
        for (auto iter = ss.begin(); iter != ss.end(); ++iter) {
26,162✔
1724
            auto sf = *iter;
24,696✔
1725

1726
            if (iter.index() >= this->wlf_field_defs.size()) {
24,696✔
1727
                sa.emplace_back(line_range{sf.sf_begin, -1},
×
1728
                                SA_INVALID.value("extra fields detected"));
×
1729
                return;
×
1730
            }
1731

1732
            const auto& fd = this->wlf_field_defs[iter.index()];
24,696✔
1733

1734
            if (sf == "-") {
24,696✔
1735
                sf.invalidate();
4,300✔
1736
            }
1737

1738
            auto lr = line_range(sf.sf_begin, sf.sf_end);
24,696✔
1739

1740
            if (lr.is_valid()) {
24,696✔
1741
                if (fd.fd_meta.lvm_name == F_DATE) {
20,396✔
1742
                    date_lr = lr;
1,444✔
1743
                } else if (fd.fd_meta.lvm_name == F_TIME) {
18,952✔
1744
                    time_lr = lr;
1,458✔
1745
                }
1746
                values.lvv_values.emplace_back(fd.fd_meta, sbr, lr);
20,396✔
1747
                if (sf.startswith("\"")) {
20,396✔
1748
                    auto& meta = values.lvv_values.back().lv_meta;
28✔
1749

1750
                    if (meta.lvm_kind == value_kind_t::VALUE_TEXT) {
28✔
1751
                        meta.lvm_kind = value_kind_t::VALUE_W3C_QUOTED;
26✔
1752
                    } else {
1753
                        meta.lvm_kind = value_kind_t::VALUE_NULL;
2✔
1754
                    }
1755
                }
1756
            } else {
1757
                values.lvv_values.emplace_back(fd.fd_meta);
4,300✔
1758
            }
1759
            if (fd.fd_root_meta != nullptr) {
24,696✔
1760
                values.lvv_values.back().lv_meta.lvm_user_hidden
20,318✔
1761
                    = fd.fd_root_meta->lvm_user_hidden;
20,318✔
1762
            }
1763
        }
1764
        if (time_lr) {
1,466✔
1765
            auto ts_lr = time_lr.value();
1,458✔
1766
            if (date_lr) {
1,458✔
1767
                if (date_lr->lr_end + 1 == time_lr->lr_start) {
1,443✔
1768
                    ts_lr.lr_start = date_lr->lr_start;
1,442✔
1769
                    ts_lr.lr_end = time_lr->lr_end;
1,442✔
1770
                }
1771
            }
1772

1773
            sa.emplace_back(ts_lr, L_TIMESTAMP.value());
1,458✔
1774
        }
1775
        log_format::annotate(lf, line_number, sa, values, annotate_module);
1,466✔
1776
    }
1777

1778
    const logline_value_stats* stats_for_value(
×
1779
        const intern_string_t& name) const override
1780
    {
1781
        const logline_value_stats* retval = nullptr;
×
1782

1783
        for (const auto& wlf_field_def : this->wlf_field_defs) {
×
1784
            if (wlf_field_def.fd_meta.lvm_name == name) {
×
1785
                if (!wlf_field_def.fd_numeric_index) {
×
1786
                    break;
×
1787
                }
1788
                retval = &this->lf_value_stats[wlf_field_def.fd_numeric_index
1789
                                                   .value()];
×
1790
                break;
×
1791
            }
1792
        }
1793

1794
        return retval;
×
1795
    }
1796

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

1811
        auto fd_iter = FIELD_META.find(field_name);
×
1812
        if (fd_iter == FIELD_META.end()) {
×
1813
            return false;
×
1814
        }
1815

1816
        fd_iter->second.lvm_user_hidden = val;
×
1817

1818
        return true;
×
1819
    }
1820

1821
    std::map<intern_string_t, logline_value_meta> get_field_states() override
52✔
1822
    {
1823
        std::map<intern_string_t, logline_value_meta> retval;
52✔
1824

1825
        for (const auto& fd : FIELD_META) {
108✔
1826
            retval.emplace(fd.first, fd.second);
56✔
1827
        }
1828

1829
        return retval;
52✔
1830
    }
×
1831

1832
    std::shared_ptr<log_format> specialized(int fmt_lock = -1) override
14✔
1833
    {
1834
        auto retval = std::make_shared<w3c_log_format>(*this);
14✔
1835

1836
        retval->lf_specialized = true;
14✔
1837
        return retval;
28✔
1838
    }
14✔
1839

1840
    class w3c_log_table : public log_format_vtab_impl {
1841
    public:
1842
        explicit w3c_log_table(const w3c_log_format& format)
11✔
1843
            : log_format_vtab_impl(format), wlt_format(format)
11✔
1844
        {
1845
        }
11✔
1846

1847
        void get_columns(std::vector<vtab_column>& cols) const override
14✔
1848
        {
1849
            for (const auto& fd : get_known_fields()) {
238✔
1850
                auto type_pair = log_vtab_impl::logline_value_to_sqlite_type(
224✔
1851
                    fd.fd_meta.lvm_kind);
224✔
1852

1853
                cols.emplace_back(fd.fd_meta.lvm_name.to_string(),
224✔
1854
                                  type_pair.first,
1855
                                  fd.fd_collator,
224✔
1856
                                  false,
448✔
1857
                                  "",
1858
                                  type_pair.second);
1859
            }
1860
            cols.emplace_back("x_fields");
14✔
1861
            cols.back().with_comment(
28✔
1862
                "A JSON-object that contains fields that are not first-class "
1863
                "columns");
1864
            for (const auto& fs : get_known_struct_fields()) {
70✔
1865
                cols.emplace_back(fs.fs_struct_name.to_string());
56✔
1866
            }
1867
        };
14✔
1868

1869
        void get_foreign_keys(
3✔
1870
            std::unordered_set<std::string>& keys_inout) const override
1871
        {
1872
            this->log_vtab_impl::get_foreign_keys(keys_inout);
3✔
1873

1874
            for (const auto& fd : get_known_fields()) {
51✔
1875
                if (fd.fd_meta.lvm_identifier || fd.fd_meta.lvm_foreign_key) {
48✔
1876
                    keys_inout.emplace(fd.fd_meta.lvm_name.to_string());
30✔
1877
                }
1878
            }
1879
        }
3✔
1880

1881
        const w3c_log_format& wlt_format;
1882
    };
1883

1884
    static std::map<intern_string_t, std::shared_ptr<w3c_log_table>>&
1885
    get_tables()
11✔
1886
    {
1887
        static std::map<intern_string_t, std::shared_ptr<w3c_log_table>> retval;
11✔
1888

1889
        return retval;
11✔
1890
    }
1891

1892
    std::shared_ptr<log_vtab_impl> get_vtab_impl() const override
638✔
1893
    {
1894
        if (this->wlf_format_name.empty()) {
638✔
1895
            return nullptr;
627✔
1896
        }
1897

1898
        std::shared_ptr<w3c_log_table> retval = nullptr;
11✔
1899

1900
        auto& tables = get_tables();
11✔
1901
        const auto iter = tables.find(this->wlf_format_name);
11✔
1902
        if (iter == tables.end()) {
11✔
1903
            retval = std::make_shared<w3c_log_table>(*this);
11✔
1904
            tables[this->wlf_format_name] = retval;
11✔
1905
        }
1906

1907
        return retval;
11✔
1908
    }
11✔
1909

1910
    void get_subline(const logline& ll,
1,629✔
1911
                     shared_buffer_ref& sbr,
1912
                     subline_options opts) override
1913
    {
1914
    }
1,629✔
1915

1916
    date_time_scanner wlf_time_scanner;
1917
    intern_string_t wlf_format_name;
1918
    std::vector<field_def> wlf_field_defs;
1919
};
1920

1921
std::unordered_map<const intern_string_t, logline_value_meta>
1922
    w3c_log_format::FIELD_META;
1923

1924
const intern_string_t w3c_log_format::F_DATE = intern_string::lookup("date");
1925
const intern_string_t w3c_log_format::F_TIME = intern_string::lookup("time");
1926

1927
struct logfmt_pair_handler {
1928
    explicit logfmt_pair_handler(date_time_scanner& dts) : lph_dt_scanner(dts)
11,885✔
1929
    {
1930
    }
11,885✔
1931

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

1957
    date_time_scanner& lph_dt_scanner;
1958
    bool lph_found_time{false};
1959
    exttm lph_time_tm;
1960
    timeval lph_tv{0, 0};
1961
    log_level_t lph_level{log_level_t::LEVEL_INFO};
1962
    string_fragment lph_key_frag{""};
1963
};
1964

1965
class logfmt_format : public log_format {
1966
public:
1967
    const intern_string_t get_name() const override
14,573✔
1968
    {
1969
        const static intern_string_t NAME = intern_string::lookup("logfmt_log");
16,055✔
1970

1971
        return NAME;
14,573✔
1972
    }
1973

1974
    class logfmt_log_table : public log_format_vtab_impl {
1975
    public:
1976
        logfmt_log_table(const log_format& format)
627✔
1977
            : log_format_vtab_impl(format)
627✔
1978
        {
1979
        }
627✔
1980

1981
        void get_columns(std::vector<vtab_column>& cols) const override
628✔
1982
        {
1983
            static const auto FIELDS = std::string("fields");
1,882✔
1984

1985
            cols.emplace_back(FIELDS);
628✔
1986
        }
628✔
1987
    };
1988

1989
    std::shared_ptr<log_vtab_impl> get_vtab_impl() const override
627✔
1990
    {
1991
        static auto retval = std::make_shared<logfmt_log_table>(*this);
627✔
1992

1993
        return retval;
627✔
1994
    }
1995

1996
    scan_result_t scan(logfile& lf,
11,885✔
1997
                       std::vector<logline>& dst,
1998
                       const line_info& li,
1999
                       shared_buffer_ref& sbr,
2000
                       scan_batch_context& sbc) override
2001
    {
2002
        auto p = logfmt::parser(sbr.to_string_fragment());
11,885✔
2003
        scan_result_t retval = scan_no_match{};
11,885✔
2004
        bool done = false;
11,885✔
2005
        logfmt_pair_handler lph(this->lf_date_time);
11,885✔
2006

2007
        if (dst.empty()) {
11,885✔
2008
            auto file_options = lf.get_file_options();
1,148✔
2009

2010
            if (file_options) {
1,148✔
2011
                this->lf_date_time.dts_default_zone
2012
                    = file_options->second.fo_default_zone.pp_value;
57✔
2013
            } else {
2014
                this->lf_date_time.dts_default_zone = nullptr;
1,091✔
2015
            }
2016
        }
1,148✔
2017

2018
        while (!done) {
27,810✔
2019
            auto parse_result = p.step();
15,925✔
2020

2021
            auto value_res = parse_result.match(
2022
                [&done](const logfmt::parser::end_of_input&) -> scan_result_t {
×
2023
                    done = true;
234✔
2024
                    return scan_match{};
234✔
2025
                },
2026
                [&lph](const logfmt::parser::kvpair& kvp) -> scan_result_t {
×
2027
                    lph.lph_key_frag = kvp.first;
4,040✔
2028

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

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

2055
                                auto value_res = lph.process_value(value_frag);
343✔
2056
                                return value_res.is<scan_match>();
686✔
2057
                            };
686✔
2058

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

2074
                                return lph.process_value(unq_frag);
×
2075
                            }
2076

2077
                            return scan_match{};
343✔
2078
                        },
343✔
2079
                        [&lph](const logfmt::parser::unquoted_value& uv)
4,040✔
2080
                            -> scan_result_t {
2081
                            return lph.process_value(uv.uv_value);
3,584✔
2082
                        });
8,080✔
2083
                },
2084
                [](const logfmt::parser::error& err) -> scan_result_t {
×
2085
                    // log_error("logfmt parse error: %s", err.e_msg.c_str());
2086
                    return scan_no_match{};
11,651✔
2087
                });
15,925✔
2088
            if (value_res.is<scan_no_match>()) {
15,925✔
2089
                retval = value_res;
11,651✔
2090
                done = true;
11,651✔
2091
            }
2092
        }
15,925✔
2093

2094
        if (lph.lph_found_time) {
11,885✔
2095
            this->lf_timestamp_flags = lph.lph_time_tm.et_flags;
31✔
2096
            dst.emplace_back(
31✔
2097
                li.li_file_range.fr_offset, lph.lph_tv, lph.lph_level);
31✔
2098
            retval = scan_match{2000};
31✔
2099
        }
2100

2101
        return retval;
23,770✔
2102
    }
×
2103

2104
    void annotate(logfile* lf,
11✔
2105
                  uint64_t line_number,
2106
                  string_attrs_t& sa,
2107
                  logline_value_vector& values,
2108
                  bool annotate_module) const override
2109
    {
2110
        static const intern_string_t FIELDS_NAME
2111
            = intern_string::lookup("fields");
15✔
2112

2113
        auto& sbr = values.lvv_sbr;
11✔
2114
        auto p = logfmt::parser(sbr.to_string_fragment());
11✔
2115
        auto done = false;
11✔
2116
        auto found_body = false;
11✔
2117

2118
        while (!done) {
95✔
2119
            auto parse_result = p.step();
84✔
2120

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

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

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

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

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

2227
        if (!found_body) {
11✔
2228
            sa.emplace_back(line_range::empty_at(sbr.length()),
×
2229
                            SA_BODY.value());
×
2230
        }
2231

2232
        log_format::annotate(lf, line_number, sa, values, annotate_module);
11✔
2233
    }
11✔
2234

2235
    std::shared_ptr<log_format> specialized(int fmt_lock) override
5✔
2236
    {
2237
        auto retval = std::make_shared<logfmt_format>(*this);
5✔
2238

2239
        retval->lf_specialized = true;
5✔
2240
        return retval;
10✔
2241
    }
5✔
2242
};
2243

2244
static auto format_binder = injector::bind_multiple<log_format>()
2245
                                .add<logfmt_format>()
2246
                                .add<bro_log_format>()
2247
                                .add<w3c_log_format>()
2248
                                .add<generic_log_format>()
2249
                                .add<piper_log_format>();
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc