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

tstack / lnav / 17896141997-2532

21 Sep 2025 04:27PM UTC coverage: 64.949% (-0.009%) from 64.958%
17896141997-2532

push

github

tstack
[build] missing include

45921 of 70703 relevant lines covered (64.95%)

407184.77 hits per line

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

88.68
/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 "config.h"
44
#include "formats/logfmt/logfmt.parser.hh"
45
#include "log_vtab_impl.hh"
46
#include "ptimec.hh"
47
#include "scn/scan.h"
48
#include "sql_util.hh"
49
#include "yajlpp/yajlpp.hh"
50

51
class piper_log_format : public log_format {
52
public:
53
    const intern_string_t get_name() const override
13,021✔
54
    {
55
        static const intern_string_t RETVAL
56
            = intern_string::lookup("lnav_piper_log");
14,419✔
57

58
        return RETVAL;
13,021✔
59
    }
60

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

75
        return scan_no_match{"not a piper capture"};
10,244✔
76
    }
77

78
    static constexpr int TIMESTAMP_SIZE = 28;
79

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

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

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

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

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

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

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

142
class generic_log_format : public log_format {
143
public:
144
    static const pcre_format* get_pcre_log_formats()
10,499✔
145
    {
146
        static const pcre_format log_fmt[] = {
147
            pcre_format(
148
                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

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

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

174
            pcre_format(),
175
        };
10,499✔
176

177
        return log_fmt;
10,499✔
178
    }
179

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

186
    const intern_string_t get_name() const override
12,690✔
187
    {
188
        static const intern_string_t RETVAL
189
            = intern_string::lookup("generic_log");
14,088✔
190

191
        return RETVAL;
12,690✔
192
    }
193

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

206
        if (dst.empty()) {
10,420✔
207
            auto file_options = lf.get_file_options();
188✔
208

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

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

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

233
            if (!((log_time.et_flags & ETF_DAY_SET)
1,505✔
234
                  && (log_time.et_flags & ETF_MONTH_SET)
1,430✔
235
                  && (log_time.et_flags & ETF_YEAR_SET)))
1,430✔
236
            {
237
                this->check_for_new_year(dst, log_time, log_tv);
676✔
238
            }
239

240
            if (!(this->lf_timestamp_flags
3,010✔
241
                  & (ETF_MILLIS_SET | ETF_MICROS_SET | ETF_NANOS_SET))
1,505✔
242
                && !dst.empty()
1,190✔
243
                && dst.back().get_time<std::chrono::seconds>().count()
1,188✔
244
                    == log_tv.tv_sec
1,188✔
245
                && dst.back()
3,547✔
246
                        .get_subsecond_time<std::chrono::microseconds>()
2,357✔
247
                        .count()
852✔
248
                    != 0)
249
            {
250
                auto log_ms
251
                    = dst.back()
×
252
                          .get_subsecond_time<std::chrono::microseconds>();
×
253

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

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

271
        return scan_no_match{"no patterns matched"};
8,915✔
272
    }
273

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

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

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

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

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

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

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

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

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

334
        retval->lf_specialized = true;
49✔
335
        return retval;
98✔
336
    }
49✔
337

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

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

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

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

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

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

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

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

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

411
    return retval;
22✔
412
}
×
413

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

420
    if ((c = *find++) != '\0') {
1,572,454✔
421
        len = strlen(find);
1,572,454✔
422
        do {
423
            do {
424
                if (slen < 1 || (sc = *s) == '\0') {
6,720,699✔
425
                    return std::nullopt;
853,862✔
426
                }
427
                --slen;
5,866,837✔
428
                ++s;
5,866,837✔
429
            } while (sc != c);
5,866,837✔
430
            if (len > slen) {
718,592✔
431
                return std::nullopt;
×
432
            }
433
        } while (strncmp(s, find, len) != 0);
718,592✔
434
        s--;
718,592✔
435
    }
436
    return s;
718,592✔
437
}
438

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

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

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

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

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

470
        void update()
1,572,454✔
471
        {
472
            const separated_string& ss = this->i_parent;
1,572,454✔
473
            auto next_field
474
                = lnav_strnstr(this->i_pos,
1,572,454✔
475
                               ss.ss_separator,
1,572,454✔
476
                               ss.ss_len - (this->i_pos - ss.ss_str));
1,572,454✔
477
            if (next_field) {
1,572,454✔
478
                this->i_next_pos = next_field.value() + ss.ss_separator_len;
718,592✔
479
            } else {
480
                this->i_next_pos = ss.ss_str + ss.ss_len;
853,862✔
481
            }
482
        }
1,572,454✔
483

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

490
            return *this;
752,269✔
491
        }
492

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

498
            if (this->i_next_pos < (ss.ss_str + ss.ss_len)) {
675,465✔
499
                end = this->i_next_pos - ss.ss_str - ss.ss_separator_len;
645,824✔
500
            } else {
501
                end = this->i_next_pos - ss.ss_str;
29,641✔
502
            }
503
            return string_fragment::from_byte_range(
675,465✔
504
                ss.ss_str, this->i_pos - ss.ss_str, end);
675,465✔
505
        }
506

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

513
        bool operator!=(const iterator& other) const
785,996✔
514
        {
515
            return !(*this == other);
785,996✔
516
        }
517

518
        size_t index() const { return this->i_index; }
1,621,336✔
519
    };
520

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

523
    iterator end() { return {*this, this->ss_str + this->ss_len}; }
786,150✔
524
};
525

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

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

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

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

565
    static std::unordered_map<const intern_string_t, logline_value_meta>
566
        FIELD_META;
567

568
    static const intern_string_t get_opid_desc()
2,571✔
569
    {
570
        static const intern_string_t RETVAL = intern_string::lookup("std");
3,981✔
571

572
        return RETVAL;
2,571✔
573
    }
574

575
    bro_log_format()
705✔
576
    {
705✔
577
        this->lf_structured = true;
705✔
578
        this->lf_is_self_describing = true;
705✔
579
        this->lf_time_ordered = false;
705✔
580

581
        auto desc_v = std::make_shared<std::vector<opid_descriptor>>();
705✔
582
        desc_v->emplace({});
705✔
583
        this->lf_opid_description_def->emplace(get_opid_desc(),
1,410✔
584
                                               opid_descriptors{desc_v});
1,410✔
585
    }
705✔
586

587
    const intern_string_t get_name() const override
114,035✔
588
    {
589
        static const intern_string_t name(intern_string::lookup("bro"));
115,433✔
590

591
        return this->blf_format_name.empty() ? name : this->blf_format_name;
114,035✔
592
    }
593

594
    void clear() override
10,479✔
595
    {
596
        this->log_format::clear();
10,479✔
597
        this->blf_format_name.clear();
10,479✔
598
        this->blf_field_defs.clear();
10,479✔
599
    }
10,479✔
600

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

612
        separated_string ss(sbr.get_data(), sbr.length());
4,168✔
613
        struct timeval tv;
614
        struct exttm tm;
4,168✔
615
        bool found_ts = false;
4,168✔
616
        log_level_t level = LEVEL_INFO;
4,168✔
617
        uint8_t opid = 0;
4,168✔
618
        auto opid_cap = string_fragment::invalid();
4,168✔
619
        auto host_cap = string_fragment::invalid();
4,168✔
620

621
        ss.with_separator(this->blf_separator.get());
4,168✔
622

623
        for (auto iter = ss.begin(); iter != ss.end(); ++iter) {
122,914✔
624
            if (iter.index() == 0 && *iter == "#close") {
118,768✔
625
                return scan_match{2000};
22✔
626
            }
627

628
            if (iter.index() >= this->blf_field_defs.size()) {
118,746✔
629
                break;
×
630
            }
631

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

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

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

647
                if (!sf.empty() && sf[0] >= '4') {
3,960✔
648
                    level = LEVEL_ERROR;
20✔
649
                }
650
            } else if (UID == fd.fd_meta.lvm_name) {
110,640✔
651
                opid_cap = *iter;
4,146✔
652

653
                opid = hash_str(opid_cap.data(), opid_cap.length());
4,146✔
654
            } else if (ID_ORIG_H == fd.fd_meta.lvm_name) {
106,494✔
655
                host_cap = *iter;
4,146✔
656
            }
657

658
            if (fd.fd_numeric_index) {
118,746✔
659
                switch (fd.fd_meta.lvm_kind) {
21,288✔
660
                    case value_kind_t::VALUE_INTEGER:
21,288✔
661
                    case value_kind_t::VALUE_FLOAT: {
662
                        const auto sv = (*iter).to_string_view();
21,288✔
663
                        auto scan_float_res = scn::scan_value<double>(sv);
21,288✔
664
                        if (scan_float_res) {
21,288✔
665
                            this->lf_value_stats[fd.fd_numeric_index.value()]
17,328✔
666
                                .add_value(scan_float_res->value());
17,328✔
667
                        }
668
                        break;
21,288✔
669
                    }
670
                    default:
×
671
                        break;
×
672
                }
673
            }
674
        }
675

676
        if (found_ts) {
4,146✔
677
            if (!this->lf_specialized) {
4,146✔
678
                for (auto& ll : dst) {
198✔
679
                    ll.set_ignore(true);
176✔
680
                }
681
            }
682

683
            if (opid_cap.is_valid()) {
4,146✔
684
                auto opid_iter
685
                    = sbc.sbc_opids.insert_op(sbc.sbc_allocator, opid_cap, tv);
4,146✔
686
                opid_iter->second.otr_level_stats.update_msg_count(level);
4,146✔
687

688
                auto& otr = opid_iter->second;
4,146✔
689
                if (!otr.otr_description.lod_id && host_cap.is_valid()
6,012✔
690
                    && otr.otr_description.lod_elements.empty())
6,012✔
691
                {
692
                    otr.otr_description.lod_id = get_opid_desc();
1,866✔
693
                    otr.otr_description.lod_elements.emplace_back(
3,732✔
694
                        0, host_cap.to_string());
1,866✔
695
                }
696
            }
697
            dst.emplace_back(li.li_file_range.fr_offset, tv, level, 0, opid);
4,146✔
698
            return scan_match{2000};
4,146✔
699
        }
700
        return scan_no_match{"no header found"};
×
701
    }
702

703
    scan_result_t scan(logfile& lf,
10,457✔
704
                       std::vector<logline>& dst,
705
                       const line_info& li,
706
                       shared_buffer_ref& sbr,
707
                       scan_batch_context& sbc) override
708
    {
709
        static const auto SEP_RE
710
            = lnav::pcre2pp::code::from_const(R"(^#separator\s+(.+))");
10,457✔
711

712
        if (dst.empty()) {
10,457✔
713
            auto file_options = lf.get_file_options();
1,080✔
714

715
            if (file_options) {
1,080✔
716
                this->lf_date_time.dts_default_zone
717
                    = file_options->second.fo_default_zone.pp_value;
53✔
718
            } else {
719
                this->lf_date_time.dts_default_zone = nullptr;
1,027✔
720
            }
721
        }
1,080✔
722

723
        if (!this->blf_format_name.empty()) {
10,457✔
724
            return this->scan_int(dst, li, sbr, sbc);
4,146✔
725
        }
726

727
        if (dst.empty() || dst.size() > 20 || sbr.empty()
11,542✔
728
            || sbr.get_data()[0] == '#')
11,542✔
729
        {
730
            return scan_no_match{"no header found"};
3,972✔
731
        }
732

733
        auto line_iter = dst.begin();
2,339✔
734
        auto read_result = lf.read_line(line_iter);
2,339✔
735

736
        if (read_result.isErr()) {
2,339✔
737
            return scan_no_match{"unable to read first line"};
×
738
        }
739

740
        auto line = read_result.unwrap();
2,339✔
741
        auto md = SEP_RE.create_match_data();
2,339✔
742

743
        auto match_res = SEP_RE.capture_from(line.to_string_fragment())
2,339✔
744
                             .into(md)
2,339✔
745
                             .matches(PCRE2_NO_UTF_CHECK)
4,678✔
746
                             .ignore_error();
2,339✔
747
        if (!match_res) {
2,339✔
748
            return scan_no_match{"cannot read separator header"};
2,317✔
749
        }
750

751
        this->clear();
22✔
752

753
        auto sep = from_escaped_string(md[1]->data(), md[1]->length());
22✔
754
        this->blf_separator = intern_string::lookup(sep);
22✔
755

756
        for (++line_iter; line_iter != dst.end(); ++line_iter) {
176✔
757
            auto next_read_result = lf.read_line(line_iter);
154✔
758

759
            if (next_read_result.isErr()) {
154✔
760
                return scan_no_match{"unable to read header line"};
×
761
            }
762

763
            line = next_read_result.unwrap();
154✔
764
            separated_string ss(line.get_data(), line.length());
154✔
765

766
            ss.with_separator(this->blf_separator.get());
154✔
767
            auto iter = ss.begin();
154✔
768

769
            string_fragment directive = *iter;
154✔
770

771
            if (directive.empty() || directive[0] != '#') {
154✔
772
                continue;
×
773
            }
774

775
            ++iter;
154✔
776
            if (iter == ss.end()) {
154✔
777
                continue;
×
778
            }
779

780
            if (directive == "#set_separator") {
154✔
781
                this->blf_set_separator = intern_string::lookup(*iter);
22✔
782
            } else if (directive == "#empty_field") {
132✔
783
                this->blf_empty_field = intern_string::lookup(*iter);
22✔
784
            } else if (directive == "#unset_field") {
110✔
785
                this->blf_unset_field = intern_string::lookup(*iter);
22✔
786
            } else if (directive == "#path") {
88✔
787
                auto full_name = fmt::format(FMT_STRING("bro_{}_log"), *iter);
66✔
788
                this->blf_format_name = intern_string::lookup(full_name);
22✔
789
            } else if (directive == "#fields" && this->blf_field_defs.empty()) {
88✔
790
                do {
791
                    auto field_name
792
                        = intern_string::lookup("bro_" + sql_safe_ident(*iter));
622✔
793
                    auto common_iter = FIELD_META.find(field_name);
622✔
794
                    if (common_iter == FIELD_META.end()) {
622✔
795
                        FIELD_META.emplace(field_name,
616✔
796
                                           logline_value_meta{
1,232✔
797
                                               field_name,
798
                                               value_kind_t::VALUE_TEXT,
799
                                           });
800
                    }
801
                    this->blf_field_defs.emplace_back(
1,244✔
802
                        field_name, this->blf_field_defs.size(), this);
622✔
803
                    ++iter;
622✔
804
                } while (iter != ss.end());
622✔
805
            } else if (directive == "#types") {
44✔
806
                static const char* KNOWN_IDS[] = {
807
                    "bro_conn_uids",
808
                    "bro_fuid",
809
                    "bro_host",
810
                    "bro_info_code",
811
                    "bro_method",
812
                    "bro_mime_type",
813
                    "bro_orig_fuids",
814
                    "bro_parent_fuid",
815
                    "bro_proto",
816
                    "bro_referrer",
817
                    "bro_resp_fuids",
818
                    "bro_service",
819
                    "bro_uid",
820
                    "bro_uri",
821
                    "bro_user_agent",
822
                    "bro_username",
823
                };
824
                static const char* KNOWN_FOREIGN[] = {
825
                    "bro_status_code",
826
                };
827

828
                int numeric_count = 0;
22✔
829

830
                do {
831
                    string_fragment field_type = *iter;
622✔
832
                    auto& fd = this->blf_field_defs[iter.index() - 1];
622✔
833

834
                    if (field_type == "time") {
622✔
835
                        fd.with_kind(value_kind_t::VALUE_TIMESTAMP);
44✔
836
                    } else if (field_type == "string") {
600✔
837
                        bool ident = std::binary_search(std::begin(KNOWN_IDS),
456✔
838
                                                        std::end(KNOWN_IDS),
839
                                                        fd.fd_meta.lvm_name);
228✔
840
                        fd.with_kind(value_kind_t::VALUE_TEXT, ident);
456✔
841
                    } else if (field_type == "count") {
372✔
842
                        bool ident = std::binary_search(std::begin(KNOWN_IDS),
228✔
843
                                                        std::end(KNOWN_IDS),
844
                                                        fd.fd_meta.lvm_name);
114✔
845
                        bool foreign
846
                            = std::binary_search(std::begin(KNOWN_FOREIGN),
228✔
847
                                                 std::end(KNOWN_FOREIGN),
848
                                                 fd.fd_meta.lvm_name);
114✔
849
                        fd.with_kind(
228✔
850
                              value_kind_t::VALUE_INTEGER, ident, foreign)
851
                            .with_numeric_index(numeric_count);
114✔
852
                        numeric_count += 1;
114✔
853
                    } else if (field_type == "bool") {
258✔
854
                        fd.with_kind(value_kind_t::VALUE_BOOLEAN);
8✔
855
                    } else if (field_type == "addr") {
254✔
856
                        fd.with_kind(
88✔
857
                            value_kind_t::VALUE_TEXT, true, false, "ipaddress");
858
                    } else if (field_type == "port") {
210✔
859
                        fd.with_kind(value_kind_t::VALUE_INTEGER, true);
88✔
860
                    } else if (field_type == "interval") {
166✔
861
                        fd.with_kind(value_kind_t::VALUE_FLOAT)
4✔
862
                            .with_numeric_index(numeric_count);
2✔
863
                        numeric_count += 1;
2✔
864
                    }
865

866
                    ++iter;
622✔
867
                } while (iter != ss.end());
622✔
868

869
                this->lf_value_stats.resize(numeric_count);
22✔
870
            }
871
        }
154✔
872

873
        if (!this->blf_format_name.empty() && !this->blf_separator.empty()
44✔
874
            && !this->blf_field_defs.empty())
44✔
875
        {
876
            return this->scan_int(dst, li, sbr, sbc);
22✔
877
        }
878

879
        this->blf_format_name.clear();
×
880
        this->lf_value_stats.clear();
×
881

882
        return scan_no_match{"no header found"};
×
883
    }
2,339✔
884

885
    void annotate(logfile* lf,
29,713✔
886
                  uint64_t line_number,
887
                  string_attrs_t& sa,
888
                  logline_value_vector& values,
889
                  bool annotate_module) const override
890
    {
891
        static const intern_string_t UID = intern_string::lookup("bro_uid");
29,737✔
892

893
        auto& sbr = values.lvv_sbr;
29,713✔
894
        separated_string ss(sbr.get_data(), sbr.length());
29,713✔
895

896
        ss.with_separator(this->blf_separator.get());
29,713✔
897

898
        for (auto iter = ss.begin(); iter != ss.end(); ++iter) {
661,838✔
899
            if (iter.index() >= this->blf_field_defs.size()) {
632,329✔
900
                return;
204✔
901
            }
902

903
            const field_def& fd = this->blf_field_defs[iter.index()];
632,125✔
904
            string_fragment sf = *iter;
632,125✔
905

906
            if (sf == this->blf_empty_field) {
632,125✔
907
                sf.clear();
29,516✔
908
            } else if (sf == this->blf_unset_field) {
602,609✔
909
                sf.invalidate();
68,206✔
910
            }
911

912
            auto lr = line_range(sf.sf_begin, sf.sf_end);
632,125✔
913

914
            if (fd.fd_meta.lvm_name == TS) {
632,125✔
915
                sa.emplace_back(lr, L_TIMESTAMP.value());
29,713✔
916
            } else if (fd.fd_meta.lvm_name == UID) {
602,412✔
917
                sa.emplace_back(lr, L_OPID.value());
29,713✔
918
            }
919

920
            if (lr.is_valid()) {
632,125✔
921
                values.lvv_values.emplace_back(fd.fd_meta, sbr, lr);
563,919✔
922
            } else {
923
                values.lvv_values.emplace_back(fd.fd_meta);
68,206✔
924
            }
925
            values.lvv_values.back().lv_meta.lvm_user_hidden
632,125✔
926
                = fd.fd_root_meta->lvm_user_hidden;
632,125✔
927
        }
928

929
        log_format::annotate(lf, line_number, sa, values, annotate_module);
29,509✔
930
    }
931

932
    const logline_value_stats* stats_for_value(
33✔
933
        const intern_string_t& name) const override
934
    {
935
        const logline_value_stats* retval = nullptr;
33✔
936

937
        for (const auto& blf_field_def : this->blf_field_defs) {
495✔
938
            if (blf_field_def.fd_meta.lvm_name == name) {
495✔
939
                if (!blf_field_def.fd_numeric_index) {
33✔
940
                    break;
×
941
                }
942
                retval = &this->lf_value_stats[blf_field_def.fd_numeric_index
943
                                                   .value()];
33✔
944
                break;
33✔
945
            }
946
        }
947

948
        return retval;
33✔
949
    }
950

951
    bool hide_field(intern_string_t field_name, bool val) override
2✔
952
    {
953
        if (field_name == LOG_TIME_STR) {
2✔
954
            field_name = TS;
×
955
        }
956

957
        auto fd_iter = FIELD_META.find(field_name);
2✔
958
        if (fd_iter == FIELD_META.end()) {
2✔
959
            return false;
×
960
        }
961

962
        fd_iter->second.lvm_user_hidden = val;
2✔
963

964
        return true;
2✔
965
    }
966

967
    std::map<intern_string_t, logline_value_meta> get_field_states() override
119✔
968
    {
969
        std::map<intern_string_t, logline_value_meta> retval;
119✔
970

971
        for (const auto& fd : FIELD_META) {
467✔
972
            retval.emplace(fd.first, fd.second);
348✔
973
        }
974

975
        return retval;
119✔
976
    }
×
977

978
    std::shared_ptr<log_format> specialized(int fmt_lock = -1) override
22✔
979
    {
980
        auto retval = std::make_shared<bro_log_format>(*this);
22✔
981

982
        retval->lf_specialized = true;
22✔
983
        return retval;
44✔
984
    }
22✔
985

986
    class bro_log_table : public log_format_vtab_impl {
987
    public:
988
        explicit bro_log_table(const bro_log_format& format)
20✔
989
            : log_format_vtab_impl(format), blt_format(format)
20✔
990
        {
991
        }
20✔
992

993
        void get_columns(std::vector<vtab_column>& cols) const override
29✔
994
        {
995
            for (const auto& fd : this->blt_format.blf_field_defs) {
854✔
996
                auto type_pair = log_vtab_impl::logline_value_to_sqlite_type(
825✔
997
                    fd.fd_meta.lvm_kind);
825✔
998

999
                cols.emplace_back(fd.fd_meta.lvm_name.to_string(),
825✔
1000
                                  type_pair.first,
1001
                                  fd.fd_collator,
825✔
1002
                                  false,
1,650✔
1003
                                  "",
1004
                                  type_pair.second);
1005
            }
1006
        }
29✔
1007

1008
        void get_foreign_keys(
10✔
1009
            std::unordered_set<std::string>& keys_inout) const override
1010
        {
1011
            this->log_vtab_impl::get_foreign_keys(keys_inout);
10✔
1012

1013
            for (const auto& fd : this->blt_format.blf_field_defs) {
292✔
1014
                if (fd.fd_meta.lvm_identifier || fd.fd_meta.lvm_foreign_key) {
282✔
1015
                    keys_inout.emplace(fd.fd_meta.lvm_name.to_string());
123✔
1016
                }
1017
            }
1018
        }
10✔
1019

1020
        const bro_log_format& blt_format;
1021
    };
1022

1023
    static std::map<intern_string_t, std::shared_ptr<bro_log_table>>&
1024
    get_tables()
20✔
1025
    {
1026
        static std::map<intern_string_t, std::shared_ptr<bro_log_table>> retval;
20✔
1027

1028
        return retval;
20✔
1029
    }
1030

1031
    std::shared_ptr<log_vtab_impl> get_vtab_impl() const override
605✔
1032
    {
1033
        if (this->blf_format_name.empty()) {
605✔
1034
            return nullptr;
585✔
1035
        }
1036

1037
        std::shared_ptr<bro_log_table> retval = nullptr;
20✔
1038

1039
        auto& tables = get_tables();
20✔
1040
        const auto iter = tables.find(this->blf_format_name);
20✔
1041
        if (iter == tables.end()) {
20✔
1042
            retval = std::make_shared<bro_log_table>(*this);
20✔
1043
            tables[this->blf_format_name] = retval;
20✔
1044
        }
1045

1046
        return retval;
20✔
1047
    }
20✔
1048

1049
    void get_subline(const logline& ll,
33,721✔
1050
                     shared_buffer_ref& sbr,
1051
                     subline_options opts) override
1052
    {
1053
    }
33,721✔
1054

1055
    intern_string_t blf_format_name;
1056
    intern_string_t blf_separator;
1057
    intern_string_t blf_set_separator;
1058
    intern_string_t blf_empty_field;
1059
    intern_string_t blf_unset_field;
1060
    std::vector<field_def> blf_field_defs;
1061
};
1062

1063
std::unordered_map<const intern_string_t, logline_value_meta>
1064
    bro_log_format::FIELD_META;
1065

1066
const intern_string_t bro_log_format::TS = intern_string::lookup("bro_ts");
1067

1068
struct ws_separated_string {
1069
    const char* ss_str;
1070
    size_t ss_len;
1071

1072
    explicit ws_separated_string(const char* str = nullptr, size_t len = -1)
18,539✔
1073
        : ss_str(str), ss_len(len)
18,539✔
1074
    {
1075
    }
18,539✔
1076

1077
    struct iterator {
1078
        enum class state_t {
1079
            NORMAL,
1080
            QUOTED,
1081
        };
1082

1083
        const ws_separated_string& i_parent;
1084
        const char* i_pos;
1085
        const char* i_next_pos;
1086
        size_t i_index{0};
1087
        state_t i_state{state_t::NORMAL};
1088

1089
        iterator(const ws_separated_string& ss, const char* pos)
29,653✔
1090
            : i_parent(ss), i_pos(pos), i_next_pos(pos)
29,653✔
1091
        {
1092
            this->update();
29,653✔
1093
        }
29,653✔
1094

1095
        void update()
40,219✔
1096
        {
1097
            const auto& ss = this->i_parent;
40,219✔
1098
            bool done = false;
40,219✔
1099

1100
            while (!done && this->i_next_pos < (ss.ss_str + ss.ss_len)) {
320,886✔
1101
                switch (this->i_state) {
280,667✔
1102
                    case state_t::NORMAL:
273,909✔
1103
                        if (*this->i_next_pos == '"') {
273,909✔
1104
                            this->i_state = state_t::QUOTED;
255✔
1105
                        } else if (isspace(*this->i_next_pos)) {
273,654✔
1106
                            done = true;
24,848✔
1107
                        }
1108
                        break;
273,909✔
1109
                    case state_t::QUOTED:
6,758✔
1110
                        if (*this->i_next_pos == '"') {
6,758✔
1111
                            this->i_state = state_t::NORMAL;
255✔
1112
                        }
1113
                        break;
6,758✔
1114
                }
1115
                if (!done) {
280,667✔
1116
                    this->i_next_pos += 1;
255,819✔
1117
                }
1118
            }
1119
        }
40,219✔
1120

1121
        iterator& operator++()
10,566✔
1122
        {
1123
            const auto& ss = this->i_parent;
10,566✔
1124

1125
            this->i_pos = this->i_next_pos;
10,566✔
1126
            while (this->i_pos < (ss.ss_str + ss.ss_len)
10,566✔
1127
                   && isspace(*this->i_pos))
20,590✔
1128
            {
1129
                this->i_pos += 1;
10,024✔
1130
                this->i_next_pos += 1;
10,024✔
1131
            }
1132
            this->update();
10,566✔
1133
            this->i_index += 1;
10,566✔
1134

1135
            return *this;
10,566✔
1136
        }
1137

1138
        string_fragment operator*()
26,037✔
1139
        {
1140
            const auto& ss = this->i_parent;
26,037✔
1141
            int end = this->i_next_pos - ss.ss_str;
26,037✔
1142

1143
            return string_fragment(ss.ss_str, this->i_pos - ss.ss_str, end);
26,037✔
1144
        }
1145

1146
        bool operator==(const iterator& other) const
11,114✔
1147
        {
1148
            return (&this->i_parent == &other.i_parent)
11,114✔
1149
                && (this->i_pos == other.i_pos);
11,114✔
1150
        }
1151

1152
        bool operator!=(const iterator& other) const
8,545✔
1153
        {
1154
            return !(*this == other);
8,545✔
1155
        }
1156

1157
        size_t index() const { return this->i_index; }
15,809✔
1158
    };
1159

1160
    iterator begin() { return {*this, this->ss_str}; }
18,539✔
1161

1162
    iterator end() { return {*this, this->ss_str + this->ss_len}; }
11,114✔
1163
};
1164

1165
class w3c_log_format : public log_format {
1166
public:
1167
    static const intern_string_t F_DATE;
1168
    static const intern_string_t F_TIME;
1169

1170
    struct field_def {
1171
        const intern_string_t fd_name;
1172
        logline_value_meta fd_meta;
1173
        logline_value_meta* fd_root_meta{nullptr};
1174
        std::string fd_collator;
1175
        std::optional<size_t> fd_numeric_index;
1176

1177
        explicit field_def(const intern_string_t name)
14✔
1178
            : fd_name(name), fd_meta(intern_string::lookup(sql_safe_ident(
28✔
1179
                                         name.to_string_fragment())),
28✔
1180
                                     value_kind_t::VALUE_TEXT)
14✔
1181
        {
1182
        }
14✔
1183

1184
        field_def(const intern_string_t name, logline_value_meta meta)
59✔
1185
            : fd_name(name), fd_meta(meta)
59✔
1186
        {
1187
        }
59✔
1188

1189
        field_def(size_t col,
9,264✔
1190
                  const char* name,
1191
                  value_kind_t kind,
1192
                  bool ident = false,
1193
                  bool foreign_key = false,
1194
                  std::string coll = "")
1195
            : fd_name(intern_string::lookup(name)),
18,528✔
1196
              fd_meta(
18,528✔
1197
                  intern_string::lookup(sql_safe_ident(string_fragment(name))),
18,528✔
1198
                  kind,
1199
                  logline_value_meta::table_column{col}),
9,264✔
1200
              fd_collator(std::move(coll))
9,264✔
1201
        {
1202
            this->fd_meta.lvm_identifier = ident;
9,264✔
1203
            this->fd_meta.lvm_foreign_key = foreign_key;
9,264✔
1204
        }
9,264✔
1205

1206
        field_def& with_kind(value_kind_t kind,
1207
                             bool identifier = false,
1208
                             const std::string& collator = "")
1209
        {
1210
            this->fd_meta.lvm_kind = kind;
1211
            this->fd_meta.lvm_identifier = identifier;
1212
            this->fd_collator = collator;
1213
            return *this;
1214
        }
1215

1216
        field_def& with_numeric_index(int index)
27✔
1217
        {
1218
            this->fd_numeric_index = index;
27✔
1219
            return *this;
27✔
1220
        }
1221
    };
1222

1223
    static std::unordered_map<const intern_string_t, logline_value_meta>
1224
        FIELD_META;
1225

1226
    struct field_to_struct_t {
1227
        field_to_struct_t(const char* prefix, const char* struct_name)
2,316✔
1228
            : fs_prefix(prefix),
2,316✔
1229
              fs_struct_name(intern_string::lookup(struct_name))
4,632✔
1230
        {
1231
        }
2,316✔
1232

1233
        const char* fs_prefix;
1234
        intern_string_t fs_struct_name;
1235
    };
1236

1237
    static const std::array<field_def, 16>& get_known_fields()
592✔
1238
    {
1239
        static size_t KNOWN_FIELD_INDEX = 0;
1240
        static const std::array<field_def, 16> RETVAL = {
1241
            field_def{
1242
                KNOWN_FIELD_INDEX++,
1243
                "cs-method",
1244
                value_kind_t::VALUE_TEXT,
1245
                true,
1246
            },
1247
            {
1248
                KNOWN_FIELD_INDEX++,
1249
                "c-ip",
1250
                value_kind_t::VALUE_TEXT,
1251
                true,
1252
                false,
1253
                "ipaddress",
1254
            },
1255
            {
1256
                KNOWN_FIELD_INDEX++,
1257
                "cs-bytes",
1258
                value_kind_t::VALUE_INTEGER,
1259
                false,
1260
            },
1261
            {
1262
                KNOWN_FIELD_INDEX++,
1263
                "cs-host",
1264
                value_kind_t::VALUE_TEXT,
1265
                true,
1266
            },
1267
            {
1268
                KNOWN_FIELD_INDEX++,
1269
                "cs-uri-stem",
1270
                value_kind_t::VALUE_TEXT,
1271
                true,
1272
                false,
1273
                "naturalnocase",
1274
            },
1275
            {
1276
                KNOWN_FIELD_INDEX++,
1277
                "cs-uri-query",
1278
                value_kind_t::VALUE_TEXT,
1279
                false,
1280
            },
1281
            {
1282
                KNOWN_FIELD_INDEX++,
1283
                "cs-username",
1284
                value_kind_t::VALUE_TEXT,
1285
                false,
1286
            },
1287
            {
1288
                KNOWN_FIELD_INDEX++,
1289
                "cs-version",
1290
                value_kind_t::VALUE_TEXT,
1291
                true,
1292
            },
1293
            {
1294
                KNOWN_FIELD_INDEX++,
1295
                "s-ip",
1296
                value_kind_t::VALUE_TEXT,
1297
                true,
1298
                false,
1299
                "ipaddress",
1300
            },
1301
            {
1302
                KNOWN_FIELD_INDEX++,
1303
                "s-port",
1304
                value_kind_t::VALUE_INTEGER,
1305
                true,
1306
            },
1307
            {
1308
                KNOWN_FIELD_INDEX++,
1309
                "s-computername",
1310
                value_kind_t::VALUE_TEXT,
1311
                true,
1312
            },
1313
            {
1314
                KNOWN_FIELD_INDEX++,
1315
                "s-sitename",
1316
                value_kind_t::VALUE_TEXT,
1317
                true,
1318
            },
1319
            {
1320
                KNOWN_FIELD_INDEX++,
1321
                "sc-bytes",
1322
                value_kind_t::VALUE_INTEGER,
1323
                false,
1324
            },
1325
            {
1326
                KNOWN_FIELD_INDEX++,
1327
                "sc-status",
1328
                value_kind_t::VALUE_INTEGER,
1329
                false,
1330
                true,
1331
            },
1332
            {
1333
                KNOWN_FIELD_INDEX++,
1334
                "sc-substatus",
1335
                value_kind_t::VALUE_INTEGER,
1336
                false,
1337
            },
1338
            {
1339
                KNOWN_FIELD_INDEX++,
1340
                "time-taken",
1341
                value_kind_t::VALUE_FLOAT,
1342
                false,
1343
            },
1344
        };
1,750✔
1345

1346
        return RETVAL;
592✔
1347
    }
1348

1349
    static const std::array<field_to_struct_t, 4>& get_known_struct_fields()
589✔
1350
    {
1351
        static const std::array<field_to_struct_t, 4> RETVAL = {
1352
            field_to_struct_t{"cs(", "cs_headers"},
1353
            {"sc(", "sc_headers"},
1354
            {"rs(", "rs_headers"},
1355
            {"sr(", "sr_headers"},
1356
        };
589✔
1357

1358
        return RETVAL;
589✔
1359
    }
1360

1361
    w3c_log_format()
705✔
1362
    {
705✔
1363
        this->lf_is_self_describing = true;
705✔
1364
        this->lf_time_ordered = false;
705✔
1365
        this->lf_structured = true;
705✔
1366
    }
705✔
1367

1368
    const intern_string_t get_name() const override
12,648✔
1369
    {
1370
        static const intern_string_t name(intern_string::lookup("w3c_log"));
14,046✔
1371

1372
        return this->wlf_format_name.empty() ? name : this->wlf_format_name;
12,648✔
1373
    }
1374

1375
    void clear() override
13,003✔
1376
    {
1377
        this->log_format::clear();
13,003✔
1378
        this->wlf_time_scanner.clear();
13,003✔
1379
        this->wlf_format_name.clear();
13,003✔
1380
        this->wlf_field_defs.clear();
13,003✔
1381
    }
13,003✔
1382

1383
    scan_result_t scan_int(std::vector<logline>& dst,
311✔
1384
                           const line_info& li,
1385
                           shared_buffer_ref& sbr)
1386
    {
1387
        static const intern_string_t F_DATE_LOCAL
1388
            = intern_string::lookup("date-local");
337✔
1389
        static const intern_string_t F_DATE_UTC
1390
            = intern_string::lookup("date-UTC");
337✔
1391
        static const intern_string_t F_TIME_LOCAL
1392
            = intern_string::lookup("time-local");
337✔
1393
        static const intern_string_t F_TIME_UTC
1394
            = intern_string::lookup("time-UTC");
337✔
1395
        static const intern_string_t F_STATUS_CODE
1396
            = intern_string::lookup("sc-status");
337✔
1397

1398
        ws_separated_string ss(sbr.get_data(), sbr.length());
311✔
1399
        timeval date_tv{0, 0}, time_tv{0, 0};
311✔
1400
        exttm date_tm, time_tm;
311✔
1401
        bool found_date = false, found_time = false;
311✔
1402
        log_level_t level = LEVEL_INFO;
311✔
1403

1404
        for (auto iter = ss.begin(); iter != ss.end(); ++iter) {
4,341✔
1405
            if (iter.index() >= this->wlf_field_defs.size()) {
4,080✔
1406
                level = LEVEL_INVALID;
1✔
1407
                break;
1✔
1408
            }
1409

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

1413
            if (sf.startswith("#")) {
4,079✔
1414
                if (sf == "#Date:") {
49✔
1415
                    auto sbr_sf_opt
1416
                        = sbr.to_string_fragment().consume_n(sf.length());
13✔
1417

1418
                    if (sbr_sf_opt) {
13✔
1419
                        auto sbr_sf = sbr_sf_opt.value().trim();
13✔
1420
                        date_time_scanner dts;
13✔
1421
                        exttm tm;
13✔
1422
                        timeval tv;
1423

1424
                        if (dts.scan(sbr_sf.data(),
13✔
1425
                                     sbr_sf.length(),
13✔
1426
                                     nullptr,
1427
                                     &tm,
1428
                                     tv))
1429
                        {
1430
                            this->lf_date_time.set_base_time(tv.tv_sec,
12✔
1431
                                                             tm.et_tm);
1432
                            this->wlf_time_scanner.set_base_time(tv.tv_sec,
12✔
1433
                                                                 tm.et_tm);
1434
                        }
1435
                    }
1436
                }
1437
                dst.emplace_back(li.li_file_range.fr_offset,
49✔
1438
                                 std::chrono::microseconds{0},
×
1439
                                 LEVEL_IGNORE,
×
1440
                                 0);
49✔
1441
                return scan_match{2000};
49✔
1442
            }
1443

1444
            sf = sf.trim("\" \t");
4,030✔
1445
            if (F_DATE == fd.fd_name || F_DATE_LOCAL == fd.fd_name
7,842✔
1446
                || F_DATE_UTC == fd.fd_name)
7,842✔
1447
            {
1448
                if (this->lf_date_time.scan(
226✔
1449
                        sf.data(), sf.length(), nullptr, &date_tm, date_tv))
226✔
1450
                {
1451
                    this->lf_timestamp_flags |= date_tm.et_flags;
225✔
1452
                    found_date = true;
225✔
1453
                }
1454
            } else if (F_TIME == fd.fd_name || F_TIME_LOCAL == fd.fd_name
7,359✔
1455
                       || F_TIME_UTC == fd.fd_name)
7,359✔
1456
            {
1457
                if (this->wlf_time_scanner.scan(
257✔
1458
                        sf.data(), sf.length(), nullptr, &time_tm, time_tv))
257✔
1459
                {
1460
                    this->lf_timestamp_flags |= time_tm.et_flags;
257✔
1461
                    found_time = true;
257✔
1462
                }
1463
            } else if (F_STATUS_CODE == fd.fd_name) {
3,547✔
1464
                if (!sf.empty() && sf[0] >= '4') {
254✔
1465
                    level = LEVEL_ERROR;
206✔
1466
                }
1467
            }
1468

1469
            if (fd.fd_numeric_index) {
4,030✔
1470
                switch (fd.fd_meta.lvm_kind) {
1,338✔
1471
                    case value_kind_t::VALUE_INTEGER:
1,338✔
1472
                    case value_kind_t::VALUE_FLOAT: {
1473
                        auto scan_float_res
1474
                            = scn::scan_value<double>(sf.to_string_view());
1,338✔
1475

1476
                        if (scan_float_res) {
1,338✔
1477
                            this->lf_value_stats[fd.fd_numeric_index.value()]
1,334✔
1478
                                .add_value(scan_float_res->value());
1,334✔
1479
                        }
1480
                        break;
1,338✔
1481
                    }
1482
                    default:
×
1483
                        break;
×
1484
                }
1485
            }
1486
        }
1487

1488
        if (found_time) {
262✔
1489
            auto tm = time_tm;
257✔
1490

1491
            if (found_date) {
257✔
1492
                tm.et_tm.tm_year = date_tm.et_tm.tm_year;
225✔
1493
                tm.et_tm.tm_mday = date_tm.et_tm.tm_mday;
225✔
1494
                tm.et_tm.tm_mon = date_tm.et_tm.tm_mon;
225✔
1495
                tm.et_tm.tm_wday = date_tm.et_tm.tm_wday;
225✔
1496
                tm.et_tm.tm_yday = date_tm.et_tm.tm_yday;
225✔
1497
            }
1498

1499
            auto tv = tm.to_timeval();
257✔
1500
            if (!this->lf_specialized) {
257✔
1501
                for (auto& ll : dst) {
50✔
1502
                    ll.set_ignore(true);
40✔
1503
                }
1504
            }
1505
            dst.emplace_back(li.li_file_range.fr_offset, tv, level, 0);
257✔
1506
            return scan_match{2000};
257✔
1507
        }
1508

1509
        return scan_no_match{"no header found"};
5✔
1510
    }
1511

1512
    scan_result_t scan(logfile& lf,
10,461✔
1513
                       std::vector<logline>& dst,
1514
                       const line_info& li,
1515
                       shared_buffer_ref& sbr,
1516
                       scan_batch_context& sbc) override
1517
    {
1518
        static const auto* W3C_LOG_NAME = intern_string::lookup("w3c_log");
11,619✔
1519
        static const auto* X_FIELDS_NAME = intern_string::lookup("x_fields");
11,619✔
1520
        static const auto& KNOWN_FIELDS = get_known_fields();
10,461✔
1521
        static const auto& KNOWN_STRUCT_FIELDS = get_known_struct_fields();
10,461✔
1522
        static auto X_FIELDS_IDX = 0;
1523

1524
        if (li.li_partial) {
10,461✔
1525
            return scan_incomplete{};
18✔
1526
        }
1527

1528
        if (dst.empty()) {
10,443✔
1529
            auto file_options = lf.get_file_options();
1,078✔
1530

1531
            if (file_options) {
1,078✔
1532
                this->lf_date_time.dts_default_zone
1533
                    = file_options->second.fo_default_zone.pp_value;
53✔
1534
            } else {
1535
                this->lf_date_time.dts_default_zone = nullptr;
1,025✔
1536
            }
1537
        }
1,078✔
1538

1539
        if (!this->wlf_format_name.empty()) {
10,443✔
1540
            return this->scan_int(dst, li, sbr);
296✔
1541
        }
1542

1543
        if (dst.empty() || dst.size() > 20 || sbr.empty()
19,216✔
1544
            || sbr.get_data()[0] == '#')
19,216✔
1545
        {
1546
            return scan_no_match{"no header found"};
7,601✔
1547
        }
1548

1549
        this->clear();
2,546✔
1550

1551
        for (auto line_iter = dst.begin(); line_iter != dst.end(); ++line_iter)
20,537✔
1552
        {
1553
            auto next_read_result = lf.read_line(line_iter);
17,991✔
1554

1555
            if (next_read_result.isErr()) {
17,991✔
1556
                return scan_no_match{"unable to read first line"};
×
1557
            }
1558

1559
            auto line = next_read_result.unwrap();
17,991✔
1560
            ws_separated_string ss(line.get_data(), line.length());
17,991✔
1561
            auto iter = ss.begin();
17,991✔
1562
            const auto directive = *iter;
17,991✔
1563

1564
            if (directive.empty() || directive[0] != '#') {
17,991✔
1565
                continue;
15,422✔
1566
            }
1567

1568
            ++iter;
2,569✔
1569
            if (iter == ss.end()) {
2,569✔
1570
                continue;
41✔
1571
            }
1572

1573
            if (directive == "#Date:") {
2,528✔
1574
                date_time_scanner dts;
8✔
1575
                struct exttm tm;
8✔
1576
                struct timeval tv;
1577

1578
                if (dts.scan(line.get_data_at(directive.length() + 1),
8✔
1579
                             line.length() - directive.length() - 1,
8✔
1580
                             nullptr,
1581
                             &tm,
1582
                             tv))
1583
                {
1584
                    this->lf_date_time.set_base_time(tv.tv_sec, tm.et_tm);
7✔
1585
                    this->wlf_time_scanner.set_base_time(tv.tv_sec, tm.et_tm);
7✔
1586
                }
1587
            } else if (directive == "#Fields:" && this->wlf_field_defs.empty())
2,520✔
1588
            {
1589
                int numeric_count = 0;
15✔
1590

1591
                do {
1592
                    auto sf = (*iter).trim(")");
142✔
1593

1594
                    auto field_iter = std::find_if(
426✔
1595
                        begin(KNOWN_FIELDS),
1596
                        end(KNOWN_FIELDS),
1597
                        [&sf](auto elem) { return sf == elem.fd_name; });
1,676✔
1598
                    if (field_iter != end(KNOWN_FIELDS)) {
284✔
1599
                        this->wlf_field_defs.emplace_back(*field_iter);
69✔
1600
                        auto& fd = this->wlf_field_defs.back();
69✔
1601
                        auto common_iter = FIELD_META.find(fd.fd_meta.lvm_name);
69✔
1602
                        if (common_iter == FIELD_META.end()) {
69✔
1603
                            auto emp_res = FIELD_META.emplace(
68✔
1604
                                fd.fd_meta.lvm_name, fd.fd_meta);
68✔
1605
                            common_iter = emp_res.first;
68✔
1606
                        }
1607
                        fd.fd_root_meta = &common_iter->second;
69✔
1608
                    } else if (sf.is_one_of("date", "time")) {
73✔
1609
                        this->wlf_field_defs.emplace_back(
28✔
1610
                            intern_string::lookup(sf));
14✔
1611
                        auto& fd = this->wlf_field_defs.back();
14✔
1612
                        auto common_iter = FIELD_META.find(fd.fd_meta.lvm_name);
14✔
1613
                        if (common_iter == FIELD_META.end()) {
14✔
1614
                            auto emp_res = FIELD_META.emplace(
13✔
1615
                                fd.fd_meta.lvm_name, fd.fd_meta);
13✔
1616
                            common_iter = emp_res.first;
13✔
1617
                        }
1618
                        fd.fd_root_meta = &common_iter->second;
14✔
1619
                    } else {
1620
                        const auto fs_iter = std::find_if(
177✔
1621
                            begin(KNOWN_STRUCT_FIELDS),
1622
                            end(KNOWN_STRUCT_FIELDS),
1623
                            [&sf](auto elem) {
197✔
1624
                                return sf.startswith(elem.fs_prefix);
197✔
1625
                            });
1626
                        if (fs_iter != end(KNOWN_STRUCT_FIELDS)) {
118✔
1627
                            const intern_string_t field_name
1628
                                = intern_string::lookup(sf.substr(3));
13✔
1629
                            this->wlf_field_defs.emplace_back(
13✔
1630
                                field_name,
1631
                                logline_value_meta(
26✔
1632
                                    field_name,
1633
                                    value_kind_t::VALUE_TEXT,
1634
                                    logline_value_meta::table_column{
×
1635
                                        KNOWN_FIELDS.size() + 1
13✔
1636
                                        + std::distance(
39✔
1637
                                            begin(KNOWN_STRUCT_FIELDS),
1638
                                            fs_iter)},
1639
                                    this)
26✔
1640
                                    .with_struct_name(fs_iter->fs_struct_name));
1641
                        } else {
1642
                            const intern_string_t field_name
1643
                                = intern_string::lookup(sf);
46✔
1644
                            this->wlf_field_defs.emplace_back(
46✔
1645
                                field_name,
1646
                                logline_value_meta(
92✔
1647
                                    field_name,
1648
                                    value_kind_t::VALUE_TEXT,
1649
                                    logline_value_meta::table_column{
×
1650
                                        KNOWN_FIELDS.size() + X_FIELDS_IDX},
92✔
1651
                                    this)
92✔
1652
                                    .with_struct_name(X_FIELDS_NAME));
1653
                        }
1654
                    }
1655
                    auto& fd = this->wlf_field_defs.back();
142✔
1656
                    fd.fd_meta.lvm_format = std::make_optional(this);
142✔
1657
                    switch (fd.fd_meta.lvm_kind) {
142✔
1658
                        case value_kind_t::VALUE_FLOAT:
27✔
1659
                        case value_kind_t::VALUE_INTEGER:
1660
                            fd.with_numeric_index(numeric_count);
27✔
1661
                            numeric_count += 1;
27✔
1662
                            break;
27✔
1663
                        default:
115✔
1664
                            break;
115✔
1665
                    }
1666

1667
                    ++iter;
142✔
1668
                } while (iter != ss.end());
142✔
1669

1670
                this->wlf_format_name = W3C_LOG_NAME;
15✔
1671
                this->lf_value_stats.resize(numeric_count);
15✔
1672
            }
1673
        }
33,454✔
1674

1675
        if (!this->wlf_format_name.empty() && !this->wlf_field_defs.empty()) {
2,546✔
1676
            return this->scan_int(dst, li, sbr);
15✔
1677
        }
1678

1679
        this->wlf_format_name.clear();
2,531✔
1680
        this->lf_value_stats.clear();
2,531✔
1681

1682
        return scan_no_match{"no header found"};
2,531✔
1683
    }
1684

1685
    void annotate(logfile* lf,
237✔
1686
                  uint64_t line_number,
1687
                  string_attrs_t& sa,
1688
                  logline_value_vector& values,
1689
                  bool annotate_module) const override
1690
    {
1691
        auto& sbr = values.lvv_sbr;
237✔
1692
        ws_separated_string ss(sbr.get_data(), sbr.length());
237✔
1693
        std::optional<line_range> date_lr;
237✔
1694
        std::optional<line_range> time_lr;
237✔
1695

1696
        for (auto iter = ss.begin(); iter != ss.end(); ++iter) {
4,062✔
1697
            auto sf = *iter;
3,825✔
1698

1699
            if (iter.index() >= this->wlf_field_defs.size()) {
3,825✔
1700
                sa.emplace_back(line_range{sf.sf_begin, -1},
×
1701
                                SA_INVALID.value("extra fields detected"));
×
1702
                return;
×
1703
            }
1704

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

1707
            if (sf == "-") {
3,825✔
1708
                sf.invalidate();
659✔
1709
            }
1710

1711
            auto lr = line_range(sf.sf_begin, sf.sf_end);
3,825✔
1712

1713
            if (lr.is_valid()) {
3,825✔
1714
                if (fd.fd_meta.lvm_name == F_DATE) {
3,166✔
1715
                    date_lr = lr;
215✔
1716
                } else if (fd.fd_meta.lvm_name == F_TIME) {
2,951✔
1717
                    time_lr = lr;
229✔
1718
                }
1719
                values.lvv_values.emplace_back(fd.fd_meta, sbr, lr);
3,166✔
1720
                if (sf.startswith("\"")) {
3,166✔
1721
                    auto& meta = values.lvv_values.back().lv_meta;
28✔
1722

1723
                    if (meta.lvm_kind == value_kind_t::VALUE_TEXT) {
28✔
1724
                        meta.lvm_kind = value_kind_t::VALUE_W3C_QUOTED;
26✔
1725
                    } else {
1726
                        meta.lvm_kind = value_kind_t::VALUE_NULL;
2✔
1727
                    }
1728
                }
1729
            } else {
1730
                values.lvv_values.emplace_back(fd.fd_meta);
659✔
1731
            }
1732
            if (fd.fd_root_meta != nullptr) {
3,825✔
1733
                values.lvv_values.back().lv_meta.lvm_user_hidden
3,128✔
1734
                    = fd.fd_root_meta->lvm_user_hidden;
3,128✔
1735
            }
1736
        }
1737
        if (time_lr) {
237✔
1738
            auto ts_lr = time_lr.value();
229✔
1739
            if (date_lr) {
229✔
1740
                if (date_lr->lr_end + 1 == time_lr->lr_start) {
214✔
1741
                    ts_lr.lr_start = date_lr->lr_start;
213✔
1742
                    ts_lr.lr_end = time_lr->lr_end;
213✔
1743
                }
1744
            }
1745

1746
            sa.emplace_back(ts_lr, L_TIMESTAMP.value());
229✔
1747
        }
1748
        log_format::annotate(lf, line_number, sa, values, annotate_module);
237✔
1749
    }
1750

1751
    const logline_value_stats* stats_for_value(
×
1752
        const intern_string_t& name) const override
1753
    {
1754
        const logline_value_stats* retval = nullptr;
×
1755

1756
        for (const auto& wlf_field_def : this->wlf_field_defs) {
×
1757
            if (wlf_field_def.fd_meta.lvm_name == name) {
×
1758
                if (!wlf_field_def.fd_numeric_index) {
×
1759
                    break;
×
1760
                }
1761
                retval = &this->lf_value_stats[wlf_field_def.fd_numeric_index
1762
                                                   .value()];
×
1763
                break;
×
1764
            }
1765
        }
1766

1767
        return retval;
×
1768
    }
1769

1770
    bool hide_field(const intern_string_t field_name, bool val) override
×
1771
    {
1772
        if (field_name == LOG_TIME_STR) {
×
1773
            auto date_iter = FIELD_META.find(F_DATE);
×
1774
            auto time_iter = FIELD_META.find(F_TIME);
×
1775
            if (date_iter == FIELD_META.end() || time_iter == FIELD_META.end())
×
1776
            {
1777
                return false;
×
1778
            }
1779
            date_iter->second.lvm_user_hidden = val;
×
1780
            time_iter->second.lvm_user_hidden = val;
×
1781
            return true;
×
1782
        }
1783

1784
        auto fd_iter = FIELD_META.find(field_name);
×
1785
        if (fd_iter == FIELD_META.end()) {
×
1786
            return false;
×
1787
        }
1788

1789
        fd_iter->second.lvm_user_hidden = val;
×
1790

1791
        return true;
×
1792
    }
1793

1794
    std::map<intern_string_t, logline_value_meta> get_field_states() override
119✔
1795
    {
1796
        std::map<intern_string_t, logline_value_meta> retval;
119✔
1797

1798
        for (const auto& fd : FIELD_META) {
119✔
1799
            retval.emplace(fd.first, fd.second);
×
1800
        }
1801

1802
        return retval;
119✔
1803
    }
×
1804

1805
    std::shared_ptr<log_format> specialized(int fmt_lock = -1) override
10✔
1806
    {
1807
        auto retval = std::make_shared<w3c_log_format>(*this);
10✔
1808

1809
        retval->lf_specialized = true;
10✔
1810
        return retval;
20✔
1811
    }
10✔
1812

1813
    class w3c_log_table : public log_format_vtab_impl {
1814
    public:
1815
        explicit w3c_log_table(const w3c_log_format& format)
7✔
1816
            : log_format_vtab_impl(format), wlt_format(format)
7✔
1817
        {
1818
        }
7✔
1819

1820
        void get_columns(std::vector<vtab_column>& cols) const override
10✔
1821
        {
1822
            for (const auto& fd : get_known_fields()) {
170✔
1823
                auto type_pair = log_vtab_impl::logline_value_to_sqlite_type(
160✔
1824
                    fd.fd_meta.lvm_kind);
160✔
1825

1826
                cols.emplace_back(fd.fd_meta.lvm_name.to_string(),
160✔
1827
                                  type_pair.first,
1828
                                  fd.fd_collator,
160✔
1829
                                  false,
320✔
1830
                                  "",
1831
                                  type_pair.second);
1832
            }
1833
            cols.emplace_back("x_fields");
10✔
1834
            cols.back().with_comment(
20✔
1835
                "A JSON-object that contains fields that are not first-class "
1836
                "columns");
1837
            for (const auto& fs : get_known_struct_fields()) {
50✔
1838
                cols.emplace_back(fs.fs_struct_name.to_string());
40✔
1839
            }
1840
        };
10✔
1841

1842
        void get_foreign_keys(
3✔
1843
            std::unordered_set<std::string>& keys_inout) const override
1844
        {
1845
            this->log_vtab_impl::get_foreign_keys(keys_inout);
3✔
1846

1847
            for (const auto& fd : get_known_fields()) {
51✔
1848
                if (fd.fd_meta.lvm_identifier || fd.fd_meta.lvm_foreign_key) {
48✔
1849
                    keys_inout.emplace(fd.fd_meta.lvm_name.to_string());
30✔
1850
                }
1851
            }
1852
        }
3✔
1853

1854
        const w3c_log_format& wlt_format;
1855
    };
1856

1857
    static std::map<intern_string_t, std::shared_ptr<w3c_log_table>>&
1858
    get_tables()
7✔
1859
    {
1860
        static std::map<intern_string_t, std::shared_ptr<w3c_log_table>> retval;
7✔
1861

1862
        return retval;
7✔
1863
    }
1864

1865
    std::shared_ptr<log_vtab_impl> get_vtab_impl() const override
592✔
1866
    {
1867
        if (this->wlf_format_name.empty()) {
592✔
1868
            return nullptr;
585✔
1869
        }
1870

1871
        std::shared_ptr<w3c_log_table> retval = nullptr;
7✔
1872

1873
        auto& tables = get_tables();
7✔
1874
        const auto iter = tables.find(this->wlf_format_name);
7✔
1875
        if (iter == tables.end()) {
7✔
1876
            retval = std::make_shared<w3c_log_table>(*this);
7✔
1877
            tables[this->wlf_format_name] = retval;
7✔
1878
        }
1879

1880
        return retval;
7✔
1881
    }
7✔
1882

1883
    void get_subline(const logline& ll,
344✔
1884
                     shared_buffer_ref& sbr,
1885
                     subline_options opts) override
1886
    {
1887
    }
344✔
1888

1889
    date_time_scanner wlf_time_scanner;
1890
    intern_string_t wlf_format_name;
1891
    std::vector<field_def> wlf_field_defs;
1892
};
1893

1894
std::unordered_map<const intern_string_t, logline_value_meta>
1895
    w3c_log_format::FIELD_META;
1896

1897
const intern_string_t w3c_log_format::F_DATE = intern_string::lookup("date");
1898
const intern_string_t w3c_log_format::F_TIME = intern_string::lookup("time");
1899

1900
struct logfmt_pair_handler {
1901
    explicit logfmt_pair_handler(date_time_scanner& dts) : lph_dt_scanner(dts)
10,457✔
1902
    {
1903
    }
10,457✔
1904

1905
    log_format::scan_result_t process_value(const string_fragment& value_frag)
3,546✔
1906
    {
1907
        if (this->lph_key_frag.is_one_of(
3,546✔
1908
                "timestamp"_frag, "time"_frag, "ts"_frag, "t"_frag))
1909
        {
1910
            if (!this->lph_dt_scanner.scan(value_frag.data(),
31✔
1911
                                           value_frag.length(),
31✔
1912
                                           nullptr,
1913
                                           &this->lph_time_tm,
1914
                                           this->lph_tv))
31✔
1915
            {
1916
                return log_format::scan_no_match{
×
1917
                    "timestamp value did not parse correctly"};
×
1918
            }
1919
            char buf[1024];
1920
            this->lph_dt_scanner.ftime(
31✔
1921
                buf, sizeof(buf), nullptr, this->lph_time_tm);
31✔
1922
            this->lph_found_time = true;
31✔
1923
        } else if (this->lph_key_frag.is_one_of("level"_frag, "lvl"_frag)) {
3,515✔
1924
            this->lph_level
1925
                = string2level(value_frag.data(), value_frag.length());
40✔
1926
        }
1927
        return log_format::scan_match{};
3,546✔
1928
    }
1929

1930
    date_time_scanner& lph_dt_scanner;
1931
    bool lph_found_time{false};
1932
    exttm lph_time_tm;
1933
    timeval lph_tv{0, 0};
1934
    log_level_t lph_level{log_level_t::LEVEL_INFO};
1935
    string_fragment lph_key_frag{""};
1936
};
1937

1938
class logfmt_format : public log_format {
1939
public:
1940
    const intern_string_t get_name() const override
12,975✔
1941
    {
1942
        const static intern_string_t NAME = intern_string::lookup("logfmt_log");
14,373✔
1943

1944
        return NAME;
12,975✔
1945
    }
1946

1947
    class logfmt_log_table : public log_format_vtab_impl {
1948
    public:
1949
        logfmt_log_table(const log_format& format)
585✔
1950
            : log_format_vtab_impl(format)
585✔
1951
        {
1952
        }
585✔
1953

1954
        void get_columns(std::vector<vtab_column>& cols) const override
586✔
1955
        {
1956
            static const auto FIELDS = std::string("fields");
1,756✔
1957

1958
            cols.emplace_back(FIELDS);
586✔
1959
        }
586✔
1960
    };
1961

1962
    std::shared_ptr<log_vtab_impl> get_vtab_impl() const override
585✔
1963
    {
1964
        static auto retval = std::make_shared<logfmt_log_table>(*this);
585✔
1965

1966
        return retval;
585✔
1967
    }
1968

1969
    scan_result_t scan(logfile& lf,
10,457✔
1970
                       std::vector<logline>& dst,
1971
                       const line_info& li,
1972
                       shared_buffer_ref& sbr,
1973
                       scan_batch_context& sbc) override
1974
    {
1975
        auto p = logfmt::parser(sbr.to_string_fragment());
10,457✔
1976
        scan_result_t retval = scan_no_match{};
10,457✔
1977
        bool done = false;
10,457✔
1978
        logfmt_pair_handler lph(this->lf_date_time);
10,457✔
1979

1980
        if (dst.empty()) {
10,457✔
1981
            auto file_options = lf.get_file_options();
1,090✔
1982

1983
            if (file_options) {
1,090✔
1984
                this->lf_date_time.dts_default_zone
1985
                    = file_options->second.fo_default_zone.pp_value;
53✔
1986
            } else {
1987
                this->lf_date_time.dts_default_zone = nullptr;
1,037✔
1988
            }
1989
        }
1,090✔
1990

1991
        while (!done) {
24,460✔
1992
            auto parse_result = p.step();
14,003✔
1993

1994
            auto value_res = parse_result.match(
1995
                [&done](const logfmt::parser::end_of_input&) -> scan_result_t {
×
1996
                    done = true;
215✔
1997
                    return scan_match{};
215✔
1998
                },
1999
                [&lph](const logfmt::parser::kvpair& kvp) -> scan_result_t {
×
2000
                    lph.lph_key_frag = kvp.first;
3,546✔
2001

2002
                    return kvp.second.match(
2003
                        [](const logfmt::parser::bool_value& bv)
×
2004
                            -> scan_result_t { return scan_match{}; },
×
2005
                        [&lph](const logfmt::parser::float_value& fv)
×
2006
                            -> scan_result_t {
2007
                            return lph.process_value(fv.fv_str_value);
5✔
2008
                        },
2009
                        [&lph](const logfmt::parser::int_value& iv)
×
2010
                            -> scan_result_t {
2011
                            return lph.process_value(iv.iv_str_value);
108✔
2012
                        },
2013
                        [&lph](const logfmt::parser::quoted_value& qv)
×
2014
                            -> scan_result_t {
2015
                            auto_mem<yajl_handle_t> handle(yajl_free);
313✔
2016
                            yajl_callbacks cb;
2017
                            scan_result_t retval;
313✔
2018

2019
                            memset(&cb, 0, sizeof(cb));
313✔
2020
                            handle = yajl_alloc(&cb, nullptr, &lph);
313✔
2021
                            cb.yajl_string = +[](void* ctx,
626✔
2022
                                                 const unsigned char* str,
2023
                                                 size_t len,
2024
                                                 yajl_string_props_t*) -> int {
2025
                                auto& lph = *((logfmt_pair_handler*) ctx);
313✔
2026
                                string_fragment value_frag{str, 0, (int) len};
313✔
2027

2028
                                auto value_res = lph.process_value(value_frag);
313✔
2029
                                return value_res.is<scan_match>();
626✔
2030
                            };
626✔
2031

2032
                            if (yajl_parse(
313✔
2033
                                    handle,
2034
                                    (const unsigned char*) qv.qv_value.data(),
313✔
2035
                                    qv.qv_value.length())
313✔
2036
                                    != yajl_status_ok
2037
                                || yajl_complete_parse(handle)
313✔
2038
                                    != yajl_status_ok)
2039
                            {
2040
                                log_debug("json parsing failed");
×
2041
                                string_fragment unq_frag{
2042
                                    qv.qv_value.sf_string,
×
2043
                                    qv.qv_value.sf_begin + 1,
×
2044
                                    qv.qv_value.sf_end - 1,
×
2045
                                };
2046

2047
                                return lph.process_value(unq_frag);
×
2048
                            }
2049

2050
                            return scan_match{};
313✔
2051
                        },
313✔
2052
                        [&lph](const logfmt::parser::unquoted_value& uv)
3,546✔
2053
                            -> scan_result_t {
2054
                            return lph.process_value(uv.uv_value);
3,120✔
2055
                        });
7,092✔
2056
                },
2057
                [](const logfmt::parser::error& err) -> scan_result_t {
×
2058
                    // log_error("logfmt parse error: %s", err.e_msg.c_str());
2059
                    return scan_no_match{};
10,242✔
2060
                });
14,003✔
2061
            if (value_res.is<scan_no_match>()) {
14,003✔
2062
                retval = value_res;
10,242✔
2063
                done = true;
10,242✔
2064
            }
2065
        }
14,003✔
2066

2067
        if (lph.lph_found_time) {
10,457✔
2068
            this->lf_timestamp_flags = lph.lph_time_tm.et_flags;
31✔
2069
            dst.emplace_back(
31✔
2070
                li.li_file_range.fr_offset, lph.lph_tv, lph.lph_level);
31✔
2071
            retval = scan_match{2000};
31✔
2072
        }
2073

2074
        return retval;
20,914✔
2075
    }
×
2076

2077
    void annotate(logfile* lf,
11✔
2078
                  uint64_t line_number,
2079
                  string_attrs_t& sa,
2080
                  logline_value_vector& values,
2081
                  bool annotate_module) const override
2082
    {
2083
        static const intern_string_t FIELDS_NAME
2084
            = intern_string::lookup("fields");
15✔
2085

2086
        auto& sbr = values.lvv_sbr;
11✔
2087
        auto p = logfmt::parser(sbr.to_string_fragment());
11✔
2088
        auto done = false;
11✔
2089
        auto found_body = false;
11✔
2090

2091
        while (!done) {
95✔
2092
            auto parse_result = p.step();
84✔
2093

2094
            done = parse_result.match(
168✔
2095
                [](const logfmt::parser::end_of_input&) { return true; },
11✔
2096
                [this, &sa, &values, &found_body](
×
2097
                    const logfmt::parser::kvpair& kvp) {
2098
                    auto value_frag = kvp.second.match(
73✔
2099
                        [this, &kvp, &values](
×
2100
                            const logfmt::parser::bool_value& bv) {
2101
                            auto lvm = logline_value_meta{intern_string::lookup(
×
2102
                                                              kvp.first),
×
2103
                                                          value_kind_t::
2104
                                                              VALUE_INTEGER,
2105
                                                          logline_value_meta::
2106
                                                              table_column{0},
×
2107
                                                          (log_format*) this}
×
2108
                                           .with_struct_name(FIELDS_NAME);
×
2109
                            values.lvv_values.emplace_back(lvm, bv.bv_value);
×
2110

2111
                            return bv.bv_str_value;
×
2112
                        },
×
2113
                        [this, &kvp, &values](
×
2114
                            const logfmt::parser::int_value& iv) {
2115
                            auto lvm = logline_value_meta{intern_string::lookup(
×
2116
                                                              kvp.first),
×
2117
                                                          value_kind_t::
2118
                                                              VALUE_INTEGER,
2119
                                                          logline_value_meta::
2120
                                                              table_column{0},
×
2121
                                                          (log_format*) this}
×
2122
                                           .with_struct_name(FIELDS_NAME);
×
2123
                            values.lvv_values.emplace_back(lvm, iv.iv_value);
×
2124

2125
                            return iv.iv_str_value;
×
2126
                        },
×
2127
                        [this, &kvp, &values](
73✔
2128
                            const logfmt::parser::float_value& fv) {
2129
                            auto lvm = logline_value_meta{intern_string::lookup(
×
2130
                                                              kvp.first),
×
2131
                                                          value_kind_t::
2132
                                                              VALUE_INTEGER,
2133
                                                          logline_value_meta::
2134
                                                              table_column{0},
×
2135
                                                          (log_format*) this}
×
2136
                                           .with_struct_name(FIELDS_NAME);
×
2137
                            values.lvv_values.emplace_back(lvm, fv.fv_value);
×
2138

2139
                            return fv.fv_str_value;
×
2140
                        },
×
2141
                        [](const logfmt::parser::quoted_value& qv) {
×
2142
                            return qv.qv_value;
24✔
2143
                        },
2144
                        [](const logfmt::parser::unquoted_value& uv) {
×
2145
                            return uv.uv_value;
49✔
2146
                        });
2147
                    auto value_lr
2148
                        = line_range{value_frag.sf_begin, value_frag.sf_end};
73✔
2149

2150
                    auto known_field = false;
73✔
2151
                    if (kvp.first.is_one_of(
73✔
2152
                            "timestamp"_frag, "time"_frag, "ts"_frag, "t"_frag))
2153
                    {
2154
                        sa.emplace_back(value_lr, L_TIMESTAMP.value());
11✔
2155
                        known_field = true;
11✔
2156
                    } else if (kvp.first.is_one_of("level"_frag, "lvl"_frag)) {
62✔
2157
                        sa.emplace_back(value_lr, L_LEVEL.value());
11✔
2158
                        known_field = true;
11✔
2159
                    } else if (kvp.first.is_one_of("msg"_frag, "message"_frag))
51✔
2160
                    {
2161
                        sa.emplace_back(value_lr, SA_BODY.value());
11✔
2162
                        found_body = true;
11✔
2163
                    } else if (kvp.second.is<logfmt::parser::quoted_value>()
40✔
2164
                               || kvp.second
78✔
2165
                                      .is<logfmt::parser::unquoted_value>())
38✔
2166
                    {
2167
                        auto lvm
2168
                            = logline_value_meta{intern_string::lookup(
160✔
2169
                                                     kvp.first),
40✔
2170
                                                 value_frag.startswith("\"")
40✔
2171
                                                     ? value_kind_t::VALUE_JSON
2172
                                                     : value_kind_t::VALUE_TEXT,
2173
                                                 logline_value_meta::
2174
                                                     table_column{0},
40✔
2175
                                                 (log_format*) this}
80✔
2176
                                  .with_struct_name(FIELDS_NAME);
40✔
2177
                        values.lvv_values.emplace_back(lvm, value_frag);
40✔
2178
                    }
40✔
2179
                    if (known_field) {
73✔
2180
                        auto key_with_eq = kvp.first;
22✔
2181
                        key_with_eq.sf_end += 1;
22✔
2182
                        sa.emplace_back(to_line_range(key_with_eq),
22✔
2183
                                        SA_REPLACED.value());
44✔
2184
                    } else {
2185
                        sa.emplace_back(to_line_range(kvp.first),
51✔
2186
                                        VC_ROLE.value(role_t::VCR_OBJECT_KEY));
102✔
2187
                    }
2188
                    return false;
73✔
2189
                },
2190
                [line_number, &sbr](const logfmt::parser::error& err) {
84✔
2191
                    log_error("bad line %.*s", sbr.length(), sbr.get_data());
×
2192
                    log_error("%lld:logfmt parse error: %s",
×
2193
                              line_number,
2194
                              err.e_msg.c_str());
2195
                    return true;
×
2196
                });
2197
        }
84✔
2198

2199
        if (!found_body) {
11✔
2200
            sa.emplace_back(line_range::empty_at(sbr.length()),
×
2201
                            SA_BODY.value());
×
2202
        }
2203

2204
        log_format::annotate(lf, line_number, sa, values, annotate_module);
11✔
2205
    }
11✔
2206

2207
    std::shared_ptr<log_format> specialized(int fmt_lock) override
5✔
2208
    {
2209
        auto retval = std::make_shared<logfmt_format>(*this);
5✔
2210

2211
        retval->lf_specialized = true;
5✔
2212
        return retval;
10✔
2213
    }
5✔
2214
};
2215

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

© 2026 Coveralls, Inc