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

tstack / lnav / 17589970077-2502

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

push

github

tstack
[format] add fields for source file/line

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

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

13954 existing lines in 210 files now uncovered.

45516 of 69814 relevant lines covered (65.2%)

404154.37 hits per line

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

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

30
#ifndef vtab_impl_hh
31
#define vtab_impl_hh
32

33
#include <deque>
34
#include <map>
35
#include <string>
36
#include <unordered_set>
37
#include <vector>
38

39
#include <sqlite3.h>
40

41
#include "ArenaAlloc/arenaalloc.h"
42
#include "logfile_sub_source.hh"
43
#include "pcrepp/pcre2pp.hh"
44
#include "robin_hood/robin_hood.h"
45

46
class textview_curses;
47

48
enum {
49
    VT_COL_LINE_NUMBER,
50
    VT_COL_LOG_TIME,
51
    VT_COL_LEVEL,
52
    VT_COL_MAX
53
};
54

55
class logfile_sub_source;
56

57
struct msg_range {
58
    struct valid {
59
        bool contains(const vis_line_t vl) const
3,283,109✔
60
        {
61
            return this->v_min_line <= vl && vl < this->v_max_line;
3,283,109✔
62
        }
63

64
        void expand_to(const vis_line_t vl)
3,223,865✔
65
        {
66
            if (vl < this->v_min_line) {
3,223,865✔
67
                this->v_min_line = vl;
1,667✔
68
            }
69
            if (vl >= this->v_max_line) {
3,223,865✔
70
                this->v_max_line = vl + 1_vl;
687✔
71
            }
72
        }
3,223,865✔
73

74
        vis_line_t v_min_line;
75
        vis_line_t v_max_line;
76
    };
77

78
    struct empty_t {};
79

80
    using range_t = mapbox::util::variant<valid, empty_t>;
81

82
    static msg_range invalid() { return msg_range{{mapbox::util::no_init{}}}; }
213✔
83

84
    static msg_range empty() { return msg_range{empty_t()}; }
1,884✔
85

86
    std::optional<valid> get_valid() const
19,945✔
87
    {
88
        if (this->mr_value.is<valid>()) {
19,945✔
89
            return this->mr_value.get<valid>();
19,931✔
90
        }
91
        return std::nullopt;
14✔
92
    }
93

94
    bool contains(vis_line_t vl) const
3,263,457✔
95
    {
96
        return this->mr_value.match(
6,526,914✔
97
            [vl](const valid& v) { return v.contains(vl); },
6,526,896✔
98
            [](const empty_t&) { return false; });
3,263,475✔
99
    }
100

101
    msg_range& expand_to(vis_line_t vl)
3,224,085✔
102
    {
103
        this->mr_value = this->mr_value.match(
6,448,170✔
104
            [vl](valid v) {
×
105
                v.expand_to(vl);
3,223,865✔
106
                return v;
3,223,865✔
107
            },
108
            [vl](empty_t) { return valid{vl, vl + 1_vl}; });
3,224,305✔
109
        return *this;
3,224,085✔
110
    }
111

112
    msg_range& intersect(const msg_range& rhs)
19,519✔
113
    {
114
        if (this->mr_value.valid()) {
19,519✔
115
            if (rhs.mr_value.valid()) {
19,306✔
116
                this->mr_value = this->mr_value.match(
19,306✔
117
                    [&rhs](const valid& v) {
19,306✔
118
                        return rhs.mr_value.match(
19,306✔
UNCOV
119
                            [&v](const valid& rv) {
×
120
                                return range_t{valid{
19,306✔
121
                                    std::max(v.v_min_line, rv.v_min_line),
19,306✔
122
                                    std::min(v.v_max_line, rv.v_max_line),
19,306✔
123
                                }};
19,306✔
124
                            },
125
                            [](const empty_t&) { return range_t{empty_t()}; });
38,612✔
126
                    },
127
                    [](const empty_t&) { return range_t{empty_t{}}; });
38,612✔
128
            }
129
        } else {
130
            this->mr_value = rhs.mr_value;
213✔
131
        }
132

133
        return *this;
19,519✔
134
    }
135

136
    range_t mr_value;
137
};
138

139
struct log_cursor {
140
    struct string_constraint {
141
        unsigned char sc_op;
142
        std::string sc_value;
143
        std::shared_ptr<lnav::pcre2pp::code> sc_pattern;
144

145
        string_constraint(unsigned char op, std::string value);
146

147
        bool matches(const std::string& sf) const;
148
    };
149

150
    template<typename T>
151
    struct integral_constraint {
152
        unsigned char ic_op;
153
        T ic_value;
154

155
        static bool op_is_supported(unsigned char op)
7✔
156
        {
157
            switch (op) {
7✔
158
                case SQLITE_INDEX_CONSTRAINT_EQ:
7✔
159
                case SQLITE_INDEX_CONSTRAINT_IS:
160
                case SQLITE_INDEX_CONSTRAINT_NE:
161
                case SQLITE_INDEX_CONSTRAINT_ISNOT:
162
                case SQLITE_INDEX_CONSTRAINT_GT:
163
                case SQLITE_INDEX_CONSTRAINT_LE:
164
                case SQLITE_INDEX_CONSTRAINT_LT:
165
                case SQLITE_INDEX_CONSTRAINT_GE:
166
                    return true;
7✔
UNCOV
167
                default:
×
UNCOV
168
                    return false;
×
169
            }
170
        }
171

172
        integral_constraint(unsigned char op, T value)
6✔
173
            : ic_op(op), ic_value(value)
6✔
174
        {
175
        }
6✔
176

177
        bool matches(const T& value) const
216✔
178
        {
179
            switch (this->ic_op) {
216✔
180
                case SQLITE_INDEX_CONSTRAINT_EQ:
197✔
181
                case SQLITE_INDEX_CONSTRAINT_IS:
182
                    return value == this->ic_value;
197✔
UNCOV
183
                case SQLITE_INDEX_CONSTRAINT_NE:
×
184
                case SQLITE_INDEX_CONSTRAINT_ISNOT:
UNCOV
185
                    return value != this->ic_value;
×
186
                case SQLITE_INDEX_CONSTRAINT_GT:
4✔
187
                    return value > this->ic_value;
4✔
188
                case SQLITE_INDEX_CONSTRAINT_LE:
4✔
189
                    return value <= this->ic_value;
4✔
190
                case SQLITE_INDEX_CONSTRAINT_LT:
4✔
191
                    return value < this->ic_value;
4✔
192
                case SQLITE_INDEX_CONSTRAINT_GE:
7✔
193
                    return value >= this->ic_value;
7✔
UNCOV
194
                default:
×
UNCOV
195
                    return false;
×
196
            }
197
        }
198
    };
199

200
    struct column_constraint {
201
        column_constraint(int32_t col, string_constraint cons)
213✔
202
            : cc_column(col), cc_constraint(std::move(cons))
213✔
203
        {
204
        }
213✔
205

206
        int32_t cc_column;
207
        string_constraint cc_constraint;
208
    };
209

210
    vis_line_t lc_curr_line;
211
    int lc_sub_index;
212
    vis_line_t lc_end_line;
213
    vis_line_t lc_direction{1_vl};
214

215
    using level_constraint = integral_constraint<log_level_t>;
216

217
    std::optional<level_constraint> lc_level_constraint;
218
    intern_string_t lc_format_name;
219
    intern_string_t lc_pattern_name;
220
    std::optional<uint16_t> lc_opid;
221
    std::vector<string_constraint> lc_log_path;
222
    logfile* lc_last_log_path_match{nullptr};
223
    logfile* lc_last_log_path_mismatch{nullptr};
224
    std::vector<string_constraint> lc_unique_path;
225
    logfile* lc_last_unique_path_match{nullptr};
226
    logfile* lc_last_unique_path_mismatch{nullptr};
227

228
    std::vector<column_constraint> lc_indexed_columns;
229
    std::vector<vis_line_t> lc_indexed_lines;
230
    msg_range lc_indexed_lines_range = msg_range::empty();
231

232
    size_t lc_scanned_rows{0};
233

234
    enum class constraint_t {
235
        none,
236
        unique,
237
    };
238

239
    void update(unsigned char op, vis_line_t vl, constraint_t cons);
240

241
    void set_eof() { this->lc_curr_line = this->lc_end_line = 0_vl; }
242

243
    bool is_eof() const
231,417✔
244
    {
245
        return this->lc_indexed_lines.empty()
231,417✔
246
            && ((this->lc_direction > 0
365,112✔
247
                 && this->lc_curr_line >= this->lc_end_line)
125,391✔
248
                || (this->lc_direction < 0
132,597✔
249
                    && this->lc_curr_line <= this->lc_end_line));
239,721✔
250
    }
251
};
252

253
const std::string LOG_BODY = "log_body";
254
const std::string LOG_TIME = "log_time";
255

256
class log_vtab_impl {
257
public:
258
    static const std::unordered_set<string_fragment, frag_hasher>
259
        RESERVED_COLUMNS;
260

261
    enum class provenance_t {
262
        format,
263
        user,
264
    };
265

266
    struct vtab_column {
267
        vtab_column(const std::string name = "",
433,591✔
268
                    int type = SQLITE3_TEXT,
269
                    const std::string collator = "",
270
                    bool hidden = false,
271
                    const std::string comment = "",
272
                    unsigned int subtype = 0)
273
            : vc_name(name), vc_type(type), vc_collator(collator),
433,591✔
274
              vc_hidden(hidden), vc_comment(comment), vc_subtype(subtype)
433,591✔
275
        {
276
        }
433,591✔
277

278
        vtab_column& with_comment(const std::string comment)
589✔
279
        {
280
            this->vc_comment = comment;
589✔
281
            return *this;
589✔
282
        }
283

284
        std::string vc_name;
285
        int vc_type;
286
        std::string vc_collator;
287
        bool vc_hidden;
288
        std::string vc_comment;
289
        int vc_subtype;
290
    };
291

292
    static std::pair<int, unsigned int> logline_value_to_sqlite_type(
293
        value_kind_t kind);
294

295
    log_vtab_impl(const intern_string_t name)
48,444✔
296
        : vi_name(name), vi_tags_name(intern_string::lookup(
48,444✔
297
                             fmt::format(FMT_STRING("{}.log_tags"), name)))
242,220✔
298
    {
299
    }
48,444✔
300

301
    virtual ~log_vtab_impl() = default;
48,444✔
302

303
    intern_string_t get_name() const { return this->vi_name; }
242,573✔
304

305
    intern_string_t get_tags_name() const { return this->vi_tags_name; }
4✔
306

307
    const std::string& get_table_statement();
308

309
    virtual bool is_valid(log_cursor& lc, logfile_sub_source& lss);
310

311
    virtual void filter(log_cursor& lc, logfile_sub_source& lss) {}
371✔
312

313
    virtual bool next(log_cursor& lc, logfile_sub_source& lss) = 0;
314

315
    virtual void get_columns(std::vector<vtab_column>& cols) const {}
1,173✔
316

317
    virtual void get_foreign_keys(
318
        std::unordered_set<std::string>& keys_inout) const;
319

320
    virtual void get_primary_keys(std::vector<std::string>& keys_out) const {}
82,980✔
321

322
    virtual void extract(logfile* lf,
323
                         uint64_t line_number,
324
                         string_attrs_t& sa,
325
                         logline_value_vector& values);
326

327
    struct column_index {
328
        robin_hood::
329
            unordered_map<string_fragment, std::deque<vis_line_t>, frag_hasher>
330
                ci_value_to_lines;
331
        uint32_t ci_index_generation{0};
332
        msg_range ci_indexed_range = msg_range::empty();
333

334
        ArenaAlloc::Alloc<char> ci_string_arena;
335
    };
336

337
    std::map<int32_t, column_index> vi_column_indexes;
338

339
    void expand_indexes_to(
34,038✔
340
        const std::vector<log_cursor::column_constraint>& cons,
341
        const vis_line_t vl)
342
    {
343
        for (const auto& cc : cons) {
3,257,697✔
344
            this->vi_column_indexes[cc.cc_column].ci_indexed_range.expand_to(
3,223,659✔
345
                vl);
346
        }
347
    }
34,038✔
348

349
    provenance_t vi_provenance{provenance_t::format};
350
    bool vi_supports_indexes{true};
351
    int vi_column_count{0};
352

353
protected:
354
    const intern_string_t vi_name;
355
    const intern_string_t vi_tags_name;
356
    std::string vi_table_statement;
357
};
358

359
class log_format_vtab_impl : public log_vtab_impl {
360
public:
361
    log_format_vtab_impl(const log_format& format)
40,516✔
362
        : log_vtab_impl(format.get_name()), lfvi_format(format)
40,516✔
363
    {
364
    }
40,516✔
365

366
    virtual bool next(log_cursor& lc, logfile_sub_source& lss);
367

368
protected:
369
    const log_format& lfvi_format;
370
};
371

372
using sql_progress_callback_t = int (*)(const log_cursor&);
373
using sql_progress_finished_callback_t = void (*)();
374

375
struct _log_vtab_data {
376
    bool lvd_looping{true};
377
    sql_progress_callback_t lvd_progress;
378
    sql_progress_finished_callback_t lvd_finished;
379
    source_location lvd_location;
380
    attr_line_t lvd_content;
381
};
382

383
extern thread_local _log_vtab_data log_vtab_data;
384

385
class sql_progress_guard {
386
public:
387
    sql_progress_guard(sql_progress_callback_t cb,
1,627✔
388
                       sql_progress_finished_callback_t fcb,
389
                       source_location loc,
390
                       const attr_line_t& content)
391
    {
392
        log_vtab_data.lvd_looping = true;
1,627✔
393
        log_vtab_data.lvd_progress = cb;
1,627✔
394
        log_vtab_data.lvd_finished = fcb;
1,627✔
395
        log_vtab_data.lvd_location = loc;
1,627✔
396
        log_vtab_data.lvd_content = content;
1,627✔
397
    }
1,627✔
398

399
    ~sql_progress_guard()
1,627✔
400
    {
401
        if (log_vtab_data.lvd_finished) {
1,627✔
402
            log_vtab_data.lvd_finished();
1,626✔
403
        }
404
        log_vtab_data.lvd_looping = true;
1,627✔
405
        log_vtab_data.lvd_progress = nullptr;
1,627✔
406
        log_vtab_data.lvd_finished = nullptr;
1,627✔
407
        log_vtab_data.lvd_location = source_location{};
1,627✔
408
        log_vtab_data.lvd_content.clear();
1,627✔
409
    }
1,627✔
410
};
411

412
class log_vtab_manager {
413
public:
414
    using iterator = std::map<string_fragment,
415
                              std::shared_ptr<log_vtab_impl>>::const_iterator;
416

417
    log_vtab_manager(sqlite3* db, textview_curses& tc, logfile_sub_source& lss);
418
    ~log_vtab_manager();
419

420
    textview_curses* get_view() const { return &this->vm_textview; }
48,467✔
421

422
    logfile_sub_source* get_source() { return &this->vm_source; }
48,467✔
423

424
    std::string register_vtab(std::shared_ptr<log_vtab_impl> vi);
425
    std::string unregister_vtab(string_fragment name);
426

427
    std::shared_ptr<log_vtab_impl> lookup_impl(string_fragment name) const;
428

429
    std::shared_ptr<log_vtab_impl> lookup_impl(intern_string_t name) const
48,868✔
430
    {
431
        return this->lookup_impl(name.to_string_fragment());
48,868✔
432
    }
433

434
    iterator begin() const { return this->vm_impls.begin(); }
473✔
435

436
    iterator end() const { return this->vm_impls.end(); }
473✔
437

438
private:
439
    sqlite3* vm_db;
440
    textview_curses& vm_textview;
441
    logfile_sub_source& vm_source;
442
    std::map<string_fragment, std::shared_ptr<log_vtab_impl>> vm_impls;
443
};
444

445
#endif
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc