• 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

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

30
#include "field_overlay_source.hh"
31

32
#include "base/humanize.time.hh"
33
#include "base/snippet_highlighters.hh"
34
#include "command_executor.hh"
35
#include "config.h"
36
#include "log.annotate.hh"
37
#include "log_format_ext.hh"
38
#include "log_vtab_impl.hh"
39
#include "md2attr_line.hh"
40
#include "msg.text.hh"
41
#include "ptimec.hh"
42
#include "readline_highlighters.hh"
43
#include "sql_util.hh"
44
#include "vtab_module.hh"
45
#include "vtab_module_json.hh"
46

47
using namespace md4cpp::literals;
48
using namespace lnav::roles::literals;
49

50
json_string extract(const char* str);
51

52
void
53
field_overlay_source::build_field_lines(const listview_curses& lv,
2,014✔
54
                                        vis_line_t row)
55
{
56
    auto& lss = this->fos_lss;
2,014✔
57
    auto& vc = view_colors::singleton();
2,014✔
58

59
    this->fos_lines.clear();
2,014✔
60
    this->fos_row_to_field_meta.clear();
2,014✔
61

62
    if (lss.text_line_count() == 0) {
2,014✔
UNCOV
63
        this->fos_log_helper.clear();
×
64

65
        return;
2,012✔
66
    }
67

68
    auto cl = lss.at(row);
2,014✔
69
    auto file = lss.find(cl);
2,014✔
70
    auto ll = file->begin() + cl;
2,014✔
71
    auto format = file->get_format();
2,014✔
72
    bool display = false;
2,014✔
73

74
    if (ll->is_time_skewed()
2,014✔
75
        || ll->get_msg_level() == log_level_t::LEVEL_INVALID)
2,014✔
76
    {
77
        display = true;
4✔
78
    }
79
    if (!this->fos_contexts.empty()) {
2,014✔
80
        display = display || this->fos_contexts.top().c_show;
2,014✔
81
    }
82

83
    if (!display) {
2,014✔
84
        return;
2,008✔
85
    }
86

87
    if (!this->fos_log_helper.parse_line(row)) {
6✔
UNCOV
88
        return;
×
89
    }
90

91
    if (ll->get_msg_level() == LEVEL_INVALID) {
6✔
92
        for (const auto& sattr : this->fos_log_helper.ldh_line_attrs) {
8✔
93
            if (sattr.sa_type != &SA_INVALID) {
4✔
UNCOV
94
                continue;
×
95
            }
96

97
            auto emsg = fmt::format(
98
                FMT_STRING("   Invalid log message: {}"),
8✔
99
                sattr.sa_value.get<decltype(SA_INVALID)::value_type>());
4✔
100
            auto al
101
                = attr_line_t(emsg)
8✔
102
                      .with_attr(string_attr(line_range{1, 2},
4✔
103
                                             VC_GRAPHIC.value(NCACS_LLCORNER)))
8✔
104
                      .with_attr(
4✔
105
                          string_attr(line_range{0, 22},
8✔
106
                                      VC_ROLE.value(role_t::VCR_INVALID_MSG)))
8✔
107
                      .move();
4✔
108
            this->fos_lines.emplace_back(al);
4✔
109
        }
4✔
110
    }
111

112
    char old_timestamp[64], curr_timestamp[64], orig_timestamp[64];
113
    timeval curr_tv, offset_tv, orig_tv, diff_tv = {0, 0};
6✔
114
    attr_line_t time_line;
6✔
115
    auto& time_str = time_line.get_string();
6✔
116
    line_range time_lr;
6✔
117
    off_t ts_len = sql_strftime(
6✔
118
        curr_timestamp, sizeof(curr_timestamp), ll->get_timeval(), 'T');
6✔
119
    {
120
        exttm tmptm;
6✔
121

122
        tmptm.et_flags |= ETF_ZONE_SET;
6✔
123
        tmptm.et_gmtoff
124
            = lnav::local_time_to_info(
12✔
125
                  date::local_seconds{ll->get_time<std::chrono::seconds>()})
6✔
126
                  .first.offset.count();
6✔
127
        ftime_z(curr_timestamp, ts_len, sizeof(curr_timestamp), tmptm);
6✔
128
        curr_timestamp[ts_len] = '\0';
6✔
129
    }
130

131
    if (ll->is_time_skewed()) {
6✔
UNCOV
132
        time_lr.lr_start = 1;
×
UNCOV
133
        time_lr.lr_end = 2;
×
134
        time_line.with_attr(
×
135
            string_attr(time_lr, VC_GRAPHIC.value(NCACS_LLCORNER)));
×
136
        time_str.append("   Out-Of-Time-Order Message");
×
137
        time_lr.lr_start = 3;
×
138
        time_lr.lr_end = time_str.length();
×
139
        time_line.with_attr(
×
140
            string_attr(time_lr, VC_ROLE.value(role_t::VCR_SKEWED_TIME)));
×
141
        time_str.append(" --");
×
142
    }
143

144
    time_str.append(" Received Time: ");
6✔
145
    time_lr.lr_start = time_str.length();
6✔
146
    time_str.append(curr_timestamp);
6✔
147
    time_lr.lr_end = time_str.length();
6✔
148
    time_line.with_attr(
6✔
149
        string_attr(time_lr, VC_STYLE.value(text_attrs::with_bold())));
12✔
150
    time_str.append(" \u2014 ");
6✔
151
    time_lr.lr_start = time_str.length();
6✔
152
    time_str.append(humanize::time::point::from_tv(ll->get_timeval())
12✔
153
                        .with_convert_to_local(true)
6✔
154
                        .as_precise_time_ago());
12✔
155
    time_lr.lr_end = time_str.length();
6✔
156
    time_line.with_attr(
6✔
157
        string_attr(time_lr, VC_STYLE.value(text_attrs::with_bold())));
12✔
158

159
    auto time_range = find_string_attr_range(
6✔
160
        this->fos_log_helper.ldh_line_attrs, &L_TIMESTAMP);
6✔
161

162
    curr_tv = this->fos_log_helper.ldh_line->get_timeval();
6✔
163
    if (ll->is_time_skewed() && time_range.lr_end != -1) {
6✔
164
        const char* time_src
UNCOV
165
            = this->fos_log_helper.ldh_line_values.lvv_sbr.get_data()
×
UNCOV
166
            + time_range.lr_start;
×
167
        struct timeval actual_tv;
168
        date_time_scanner dts;
×
UNCOV
169
        struct exttm tm;
×
170

171
        dts.set_base_time(format->lf_date_time.dts_base_time,
×
UNCOV
172
                          format->lf_date_time.dts_base_tm.et_tm);
×
173
        dts.dts_zoned_to_local = format->lf_date_time.dts_zoned_to_local;
×
174
        if (format->lf_date_time.scan(time_src,
×
175
                                      time_range.length(),
×
176
                                      format->get_timestamp_formats(),
177
                                      &tm,
178
                                      actual_tv)
UNCOV
179
            || dts.scan(time_src, time_range.length(), nullptr, &tm, actual_tv))
×
180
        {
181
            sql_strftime(
×
182
                orig_timestamp, sizeof(orig_timestamp), actual_tv, 'T');
183
            time_str.append(";  Actual Time: ");
×
UNCOV
184
            time_lr.lr_start = time_str.length();
×
185
            time_str.append(orig_timestamp);
×
186
            time_lr.lr_end = time_str.length();
×
187
            time_line.with_attr(
×
188
                string_attr(time_lr, VC_ROLE.value(role_t::VCR_SKEWED_TIME)));
×
189

190
            timersub(&curr_tv, &actual_tv, &diff_tv);
×
UNCOV
191
            time_str.append(";  Diff: ");
×
192
            time_lr.lr_start = time_str.length();
×
193
            time_str.append(
×
194
                humanize::time::duration::from_tv(diff_tv).to_string());
×
195
            time_lr.lr_end = time_str.length();
×
196
            time_line.with_attr(
×
197
                string_attr(time_lr, VC_STYLE.value(text_attrs::with_bold())));
×
198
        }
199
    }
200

201
    offset_tv = this->fos_log_helper.ldh_file->get_time_offset();
6✔
202
    timersub(&curr_tv, &offset_tv, &orig_tv);
6✔
203
    sql_strftime(old_timestamp,
6✔
204
                 sizeof(old_timestamp),
205
                 orig_tv.tv_sec,
6✔
206
                 orig_tv.tv_usec / 1000,
6✔
207
                 'T');
208
    if (offset_tv.tv_sec || offset_tv.tv_usec) {
6✔
UNCOV
209
        time_str.append("  Pre-adjust Time: ");
×
UNCOV
210
        time_str.append(old_timestamp);
×
211
        fmt::format_to(std::back_inserter(time_str),
×
212
                       FMT_STRING("  Offset: {:+}.{:03}"),
×
213
                       offset_tv.tv_sec,
214
                       std::chrono::duration_cast<std::chrono::milliseconds>(
×
215
                           std::chrono::microseconds(offset_tv.tv_usec))
×
216
                           .count());
×
217
    }
218

219
    if (format->lf_date_time.dts_fmt_lock != -1) {
6✔
220
        const auto* ts_formats = format->get_timestamp_formats();
6✔
221
        if (ts_formats == nullptr) {
6✔
222
            ts_formats = PTIMEC_FORMAT_STR;
6✔
223
        }
224
        time_line.append("  Format: ")
6✔
225
            .append(lnav::roles::symbol(
12✔
226
                ts_formats[format->lf_date_time.dts_fmt_lock]))
6✔
227
            .append("  Default Zone: ");
6✔
228
        if (format->lf_date_time.dts_default_zone != nullptr) {
6✔
UNCOV
229
            time_line.append(lnav::roles::symbol(
×
UNCOV
230
                format->lf_date_time.dts_default_zone->name()));
×
231
        } else {
232
            time_line.append("none"_comment);
6✔
233
        }
234

235
        auto file_opts = file->get_file_options();
6✔
236
        if (file_opts) {
6✔
UNCOV
237
            time_line.append("  File Options: ")
×
UNCOV
238
                .append(lnav::roles::file(file_opts->first));
×
239
        }
240
    }
6✔
241

242
    if ((!this->fos_contexts.empty() && this->fos_contexts.top().c_show)
12✔
243
        || diff_tv.tv_sec > 0 || ll->is_time_skewed())
12✔
244
    {
245
        this->fos_lines.emplace_back(time_line);
2✔
246
    }
247

248
    if (this->fos_contexts.empty() || !this->fos_contexts.top().c_show) {
6✔
249
        return;
4✔
250
    }
251

252
    auto anchor_opt = this->fos_lss.anchor_for_row(row);
2✔
253
    if (anchor_opt) {
2✔
254
        auto permalink
255
            = attr_line_t(" Permalink: ")
2✔
256
                  .append(lnav::roles::hyperlink(anchor_opt.value()));
2✔
257
        this->fos_row_to_field_meta.emplace(
4✔
258
            this->fos_lines.size(), row_info{std::nullopt, anchor_opt.value()});
2✔
259
        this->fos_lines.emplace_back(permalink);
2✔
260
    }
2✔
261

262
    this->fos_known_key_size = LOG_BODY.length();
2✔
263
    if (!this->fos_contexts.empty()) {
2✔
264
        this->fos_known_key_size += this->fos_contexts.top().c_prefix.length();
2✔
265
    }
266
    this->fos_unknown_key_size = 0;
2✔
267

268
    for (const auto& ldh_line_value :
2✔
269
         this->fos_log_helper.ldh_line_values.lvv_values)
22✔
270
    {
271
        auto& meta = ldh_line_value.lv_meta;
18✔
272
        int this_key_size = meta.lvm_name.size();
18✔
273

274
        if (!meta.lvm_column.is<logline_value_meta::table_column>()) {
18✔
275
            continue;
10✔
276
        }
277

278
        if (!this->fos_contexts.empty()) {
8✔
279
            this_key_size += this->fos_contexts.top().c_prefix.length();
8✔
280
        }
281
        if (meta.lvm_kind == value_kind_t::VALUE_STRUCT) {
8✔
UNCOV
282
            this_key_size += 9;
×
283
        }
284
        if (!meta.lvm_struct_name.empty()) {
8✔
UNCOV
285
            this_key_size += meta.lvm_struct_name.size() + 11;
×
286
        }
287
        this->fos_known_key_size
288
            = std::max(this->fos_known_key_size, this_key_size);
8✔
289
    }
290

291
    for (auto iter = this->fos_log_helper.ldh_parser->dp_pairs.begin();
2✔
292
         iter != this->fos_log_helper.ldh_parser->dp_pairs.end();
2✔
UNCOV
293
         ++iter)
×
294
    {
295
        std::string colname
296
            = this->fos_log_helper.ldh_parser->get_element_string(
UNCOV
297
                iter->e_sub_elements->front());
×
298

299
        colname
UNCOV
300
            = this->fos_log_helper.ldh_namer->add_column(colname).to_string();
×
301
        this->fos_unknown_key_size
UNCOV
302
            = std::max(this->fos_unknown_key_size, (int) colname.length());
×
303
    }
304

305
    auto lf = this->fos_log_helper.ldh_file->get_format();
2✔
306
    if (!lf->get_pattern_regex(cl).empty()) {
2✔
307
        attr_line_t pattern_al;
1✔
308
        std::string& pattern_str = pattern_al.get_string();
1✔
309
        pattern_str = " Pattern: " + lf->get_pattern_path(cl) + " = ";
1✔
310
        int skip = pattern_str.length();
1✔
311
        pattern_str += lf->get_pattern_regex(cl);
1✔
312
        lnav::snippets::regex_highlighter(
1✔
313
            pattern_al,
314
            pattern_al.length(),
1✔
315
            line_range{skip, (int) pattern_al.length()});
1✔
316
        this->fos_lines.emplace_back(pattern_al);
1✔
317
    }
1✔
318

319
    if (this->fos_log_helper.ldh_line_values.lvv_values.empty()) {
2✔
UNCOV
320
        this->fos_lines.emplace_back(" No known message fields");
×
321
    }
322

323
    const log_format* last_format = nullptr;
2✔
324

325
    for (const auto& lv : this->fos_log_helper.ldh_line_values.lvv_values) {
20✔
326
        const auto& meta = lv.lv_meta;
18✔
327
        if (!meta.lvm_format) {
18✔
328
            continue;
10✔
329
        }
330

331
        if (!meta.lvm_column.is<logline_value_meta::table_column>()) {
18✔
332
            continue;
10✔
333
        }
334

335
        auto* curr_format = meta.lvm_format.value();
8✔
336
        auto* curr_elf = dynamic_cast<external_log_format*>(curr_format);
8✔
337
        const auto format_name = curr_format->get_name().to_string();
8✔
338
        attr_line_t al;
8✔
339
        auto value_str = lv.to_string();
8✔
340

341
        if (curr_format != last_format) {
8✔
342
            this->fos_lines.emplace_back(" Known message fields for table "
2✔
343
                                         + format_name + ":");
4✔
344
            this->fos_lines.back().with_attr(
4✔
345
                string_attr(line_range(32, 32 + format_name.length()),
4✔
346
                            VC_STYLE.value(vc.attrs_for_ident(format_name)
8✔
347
                                           | text_attrs::style::bold)));
4✔
348
            last_format = curr_format;
2✔
349
        }
350

351
        std::string field_name, orig_field_name;
8✔
352
        line_range hl_range;
8✔
353
        al.append(" ").append("|", VC_GRAPHIC.value(NCACS_LTEE)).append(" ");
8✔
354
        if (meta.lvm_struct_name.empty()) {
8✔
355
            if (curr_elf && curr_elf->elf_body_field == meta.lvm_name) {
8✔
UNCOV
356
                field_name = LOG_BODY;
×
357
            } else if (curr_elf
8✔
358
                       && curr_elf->lf_timestamp_field == meta.lvm_name)
8✔
359
            {
UNCOV
360
                field_name = LOG_TIME;
×
361
            } else {
362
                field_name = meta.lvm_name.to_string();
8✔
363
            }
364
            orig_field_name = field_name;
8✔
365
            if (!this->fos_contexts.empty()) {
8✔
366
                field_name = this->fos_contexts.top().c_prefix + field_name;
8✔
367
            }
368
            if (meta.is_hidden()) {
8✔
369
                al.append("\u25c7"_comment);
3✔
370
            } else {
371
                al.append("\u25c6"_ok);
5✔
372
            }
373
            al.append(" ");
8✔
374

375
            switch (meta.to_chart_type()) {
8✔
376
                case chart_type_t::none:
1✔
377
                    al.append("   ");
1✔
378
                    break;
1✔
379
                case chart_type_t::hist:
7✔
380
                case chart_type_t::spectro:
381
                    al.append(":bar_chart:"_emoji).append(" ");
7✔
382
                    break;
7✔
383
            }
384
            auto prefix_len = al.column_width();
8✔
385
            hl_range.lr_start = al.get_string().length();
8✔
386
            al.append(field_name);
8✔
387
            hl_range.lr_end = al.get_string().length();
8✔
388
            al.pad_to(prefix_len + this->fos_known_key_size);
8✔
389

390
            this->fos_row_to_field_meta.emplace(this->fos_lines.size(),
8✔
391
                                                row_info{meta, value_str});
16✔
392
        } else {
393
            auto jget_str = lnav::sql::mprintf("jget(%s, '/%q')",
394
                                               meta.lvm_struct_name.get(),
UNCOV
395
                                               meta.lvm_name.get());
×
UNCOV
396
            hl_range.lr_start = al.get_string().length();
×
UNCOV
397
            al.append(jget_str.in());
×
UNCOV
398
            hl_range.lr_end = al.get_string().length();
×
399

400
            this->fos_row_to_field_meta.emplace(
×
UNCOV
401
                this->fos_lines.size(), row_info{std::nullopt, value_str});
×
402
        }
403
        readline_sql_highlighter_int(
8✔
404
            al, lnav::sql::dialect::sqlite, std::nullopt, hl_range);
405

406
        al.append(" = ").append(scrub_ws(value_str.c_str()));
8✔
407

408
        this->fos_lines.emplace_back(al);
8✔
409

410
        if (meta.lvm_kind == value_kind_t::VALUE_STRUCT) {
8✔
UNCOV
411
            json_string js = extract(value_str.c_str());
×
412

413
            al.clear()
×
UNCOV
414
                .append("   extract(")
×
UNCOV
415
                .append(meta.lvm_name.get(),
×
UNCOV
416
                        VC_STYLE.value(vc.attrs_for_ident(meta.lvm_name)))
×
UNCOV
417
                .append(")")
×
UNCOV
418
                .append(this->fos_known_key_size - meta.lvm_name.size() - 9 + 3,
×
419
                        ' ')
UNCOV
420
                .append(" = ")
×
UNCOV
421
                .append(scrub_ws(string_fragment::from_bytes(js.js_content.in(),
×
422
                                                             js.js_len)));
UNCOV
423
            this->fos_lines.emplace_back(al);
×
UNCOV
424
            this->add_key_line_attrs(this->fos_known_key_size);
×
425
        }
426
    }
8✔
427

428
    if (!this->fos_log_helper.ldh_extra_json.empty()
2✔
429
        || !this->fos_log_helper.ldh_json_pairs.empty())
2✔
430
    {
431
        this->fos_lines.emplace_back(" JSON fields:");
1✔
432
    }
433

434
    for (const auto& extra_pair : this->fos_log_helper.ldh_extra_json) {
6✔
435
        auto qname = lnav::sql::mprintf("%Q", extra_pair.first.c_str());
4✔
436
        auto key_line = attr_line_t("   jget(log_raw_text, ")
4✔
437
                            .append(qname.in())
8✔
438
                            .append(")")
4✔
439
                            .move();
4✔
440
        readline_sql_highlighter(
4✔
441
            key_line, lnav::sql::dialect::sqlite, std::nullopt);
442
        auto key_size = key_line.length();
4✔
443
        key_line.append(" = ").append(scrub_ws(extra_pair.second));
4✔
444
        this->fos_row_to_field_meta.emplace(this->fos_lines.size(),
4✔
445
                                            row_info{
8✔
446
                                                std::nullopt,
447
                                                extra_pair.second,
4✔
448
                                            });
449
        this->fos_lines.emplace_back(key_line);
4✔
450
        this->add_key_line_attrs(key_size - 3);
4✔
451
    }
4✔
452

453
    for (const auto& jpairs_map : this->fos_log_helper.ldh_json_pairs) {
2✔
UNCOV
454
        const auto& jpairs = jpairs_map.second;
×
455

UNCOV
456
        for (size_t lpc = 0; lpc < jpairs.size(); lpc++) {
×
UNCOV
457
            auto key_line = attr_line_t("   ")
×
UNCOV
458
                                .append(this->fos_log_helper.format_json_getter(
×
459
                                    jpairs_map.first, lpc))
UNCOV
460
                                .move();
×
UNCOV
461
            readline_sql_highlighter(
×
462
                key_line, lnav::sql::dialect::sqlite, std::nullopt);
UNCOV
463
            auto key_size = key_line.length();
×
UNCOV
464
            key_line.append(" = ").append(scrub_ws(jpairs[lpc].wt_value));
×
UNCOV
465
            this->fos_row_to_field_meta.emplace(
×
UNCOV
466
                this->fos_lines.size(),
×
UNCOV
467
                row_info{std::nullopt, jpairs[lpc].wt_value});
×
UNCOV
468
            this->fos_lines.emplace_back(key_line);
×
UNCOV
469
            this->add_key_line_attrs(key_size - 3);
×
470
        }
471
    }
472

473
    if (!this->fos_log_helper.ldh_xml_pairs.empty()) {
2✔
474
        this->fos_lines.emplace_back(" XML fields:");
1✔
475
    }
476

477
    for (const auto& xml_pair : this->fos_log_helper.ldh_xml_pairs) {
6✔
478
        auto qname = sql_quote_ident(xml_pair.first.first.get());
4✔
479
        auto xp_call = lnav::sql::mprintf(
480
            "xpath(%Q, %s.%s)",
481
            xml_pair.first.second.c_str(),
482
            this->fos_log_helper.ldh_file->get_format()->get_name().c_str(),
8✔
483
            qname.in());
8✔
484
        auto key_line = attr_line_t("   ").append(xp_call.in()).move();
4✔
485
        readline_sql_highlighter(
4✔
486
            key_line, lnav::sql::dialect::sqlite, std::nullopt);
487
        auto key_size = key_line.length();
4✔
488
        key_line.append(" = ").append(scrub_ws(xml_pair.second));
4✔
489
        this->fos_row_to_field_meta.emplace(
8✔
490
            this->fos_lines.size(), row_info{std::nullopt, xml_pair.second});
4✔
491
        this->fos_lines.emplace_back(key_line);
4✔
492
        this->add_key_line_attrs(key_size - 3);
4✔
493
    }
4✔
494

495
    if (this->fos_log_helper.ldh_parser->dp_pairs.empty()) {
2✔
496
        this->fos_lines.emplace_back(" No discovered message fields");
2✔
497
    } else {
UNCOV
498
        this->fos_lines.emplace_back(
×
499
            " Discovered fields for logline table from message "
500
            "format: ");
501
        this->fos_lines.back().with_attr(
×
UNCOV
502
            string_attr(line_range(23, 23 + 7),
×
503
                        VC_STYLE.value(vc.attrs_for_ident("logline"))));
×
504
        auto& al = this->fos_lines.back();
×
UNCOV
505
        auto& disc_str = al.get_string();
×
506

UNCOV
507
        al.with_attr(string_attr(line_range(disc_str.length(), -1),
×
UNCOV
508
                                 VC_STYLE.value(text_attrs::with_bold())));
×
UNCOV
509
        disc_str.append(this->fos_log_helper.ldh_msg_format);
×
510
    }
511

512
    auto iter = this->fos_log_helper.ldh_parser->dp_pairs.begin();
2✔
513
    for (size_t lpc = 0; lpc < this->fos_log_helper.ldh_parser->dp_pairs.size();
2✔
UNCOV
514
         lpc++, ++iter)
×
515
    {
UNCOV
516
        auto name = this->fos_log_helper.ldh_namer->cn_names[lpc];
×
517
        auto val = this->fos_log_helper.ldh_parser->get_element_string(
UNCOV
518
            iter->e_sub_elements->back());
×
UNCOV
519
        attr_line_t al(fmt::format(FMT_STRING("   {} = {}"), name, val));
×
520

UNCOV
521
        al.with_attr(
×
UNCOV
522
            string_attr(line_range(3, 3 + name.length()),
×
UNCOV
523
                        VC_STYLE.value(vc.attrs_for_ident(name.to_string()))));
×
524

UNCOV
525
        this->fos_row_to_field_meta.emplace(this->fos_lines.size(),
×
526
                                            row_info{std::nullopt, val});
×
UNCOV
527
        this->fos_lines.emplace_back(al);
×
UNCOV
528
        this->add_key_line_attrs(
×
529
            this->fos_unknown_key_size,
530
            lpc == (this->fos_log_helper.ldh_parser->dp_pairs.size() - 1));
×
531
    }
532
}
4,030✔
533

534
void
535
field_overlay_source::build_meta_line(const listview_curses& lv,
18,087✔
536
                                      std::vector<attr_line_t>& dst,
537
                                      vis_line_t row)
538
{
539
    auto line_meta_opt = this->fos_lss.find_bookmark_metadata(row);
18,087✔
540

541
    if (!this->fos_contexts.empty()
18,087✔
542
        && this->fos_contexts.top().c_show_applicable_annotations)
18,087✔
543
    {
UNCOV
544
        if (this->fos_index_generation != this->fos_lss.lss_index_generation) {
×
UNCOV
545
            this->fos_anno_cache.clear();
×
UNCOV
546
            this->fos_index_generation = this->fos_lss.lss_index_generation;
×
547
        }
548

UNCOV
549
        auto file_and_line = this->fos_lss.find_line_with_file(row);
×
550

UNCOV
551
        if (file_and_line && !file_and_line->second->is_continued()) {
×
UNCOV
552
            auto get_res = this->fos_anno_cache.get(row);
×
UNCOV
553
            if (get_res) {
×
UNCOV
554
                auto anno_val = get_res.value();
×
UNCOV
555
                if (anno_val) {
×
UNCOV
556
                    dst.emplace_back(anno_val.value());
×
557
                }
UNCOV
558
            } else {
×
UNCOV
559
                auto applicable_anno = lnav::log::annotate::applicable(row);
×
UNCOV
560
                if (!applicable_anno.empty()
×
UNCOV
561
                    && (!line_meta_opt
×
UNCOV
562
                        || line_meta_opt.value()
×
UNCOV
563
                               ->bm_annotations.la_pairs.empty()))
×
564
                {
565
                    auto anno_msg
UNCOV
566
                        = attr_line_t(" ")
×
UNCOV
567
                              .append(":memo:"_emoji)
×
UNCOV
568
                              .append(" Annotations available, ")
×
UNCOV
569
                              .append(lv.get_selection() == row
×
570
                                          ? "use "
571
                                          : "focus on this line and use ")
UNCOV
572
                              .append(":annotate"_quoted_code)
×
573
                              .append(" to apply them")
×
UNCOV
574
                              .append(lv.get_selection() == row
×
575
                                          ? " to this line"
576
                                          : "")
UNCOV
577
                              .with_attr_for_all(
×
UNCOV
578
                                  VC_ROLE.value(role_t::VCR_COMMENT))
×
UNCOV
579
                              .move();
×
580

581
                    this->fos_anno_cache.put(row, anno_msg);
×
UNCOV
582
                    dst.emplace_back(anno_msg);
×
UNCOV
583
                } else {
×
UNCOV
584
                    this->fos_anno_cache.put(row, std::nullopt);
×
585
                }
586
            }
587
        }
588
    }
589

590
    if (!line_meta_opt) {
18,087✔
591
        return;
17,833✔
592
    }
593
    const auto* tc = dynamic_cast<const textview_curses*>(&lv);
254✔
594
    auto& vc = view_colors::singleton();
254✔
595
    const auto& line_meta = *(line_meta_opt.value());
254✔
596
    size_t filename_width = this->fos_lss.get_filename_offset();
254✔
597

598
    auto file_and_line = this->fos_lss.find_line_with_file(row);
254✔
599
    auto* format = file_and_line->first->get_format_ptr();
254✔
600
    auto field_states = format->get_field_states();
254✔
601
    auto show_opid = false;
254✔
602
    auto field_iter = field_states.find(log_format::LOG_OPID_STR);
254✔
603
    if (field_iter != field_states.end() && !field_iter->second.is_hidden()) {
254✔
604
        show_opid = true;
242✔
605
    }
606
    if (row == tc->get_selection() && !this->fos_contexts.empty()
364✔
607
        && this->fos_contexts.top().c_show)
364✔
608
    {
609
        show_opid = true;
1✔
610
    }
611
    if (show_opid && !line_meta.bm_opid.empty()) {
254✔
612
        auto al = attr_line_t()
27✔
613
                      .append(" Op ID: "_table_header)
27✔
614
                      .append(lnav::roles::identifier(line_meta.bm_opid))
54✔
615
                      .move();
27✔
616

617
        dst.emplace_back(al);
27✔
618
    }
27✔
619

620
    if (!line_meta.bm_comment.empty()) {
254✔
621
        const auto* lead = line_meta.bm_tags.empty() ? " \u2514 " : " \u251c ";
56✔
622
        md2attr_line mdal;
56✔
623
        attr_line_t al;
56✔
624

625
        auto comment_id = intern_string::lookup(fmt::format(
56✔
626
            FMT_STRING("{}-line{}-comment"),
168✔
627
            file_and_line->first->get_filename().filename().string(),
112✔
628
            std::distance(file_and_line->first->begin(),
112✔
629
                          file_and_line->second)));
56✔
630
        mdal.with_source_id(comment_id);
56✔
631
        if (tc->tc_interactive) {
56✔
UNCOV
632
            mdal.add_lnav_script_icons();
×
633
        }
634
        auto parse_res = md4cpp::parse(line_meta.bm_comment, mdal);
56✔
635
        if (parse_res.isOk()) {
56✔
636
            al = parse_res.unwrap();
56✔
637
        } else {
UNCOV
638
            log_error("%d: cannot convert comment to markdown: %s",
×
639
                      (int) row,
640
                      parse_res.unwrapErr().c_str());
UNCOV
641
            al = line_meta.bm_comment;
×
642
        }
643

644
        auto comment_lines = al.rtrim().split_lines();
56✔
645
        if (comment_lines.back().empty()) {
56✔
UNCOV
646
            comment_lines.pop_back();
×
647
        }
648
        for (size_t lpc = 0; lpc < comment_lines.size(); lpc++) {
120✔
649
            auto& comment_line = comment_lines[lpc];
64✔
650

651
            if (lpc == 0 && comment_line.empty()) {
64✔
UNCOV
652
                continue;
×
653
            }
654
            comment_line.with_attr_for_all(VC_ROLE.value(role_t::VCR_COMMENT));
64✔
655
            comment_line.insert(
64✔
656
                0, lpc == comment_lines.size() - 1 ? lead : " \u2502 ");
64✔
657
            comment_line.insert(0, filename_width, ' ');
64✔
658
            if (tc != nullptr) {
64✔
659
                auto hl = tc->get_highlights();
64✔
660
                auto hl_iter = hl.find({highlight_source_t::PREVIEW, "search"});
64✔
661

662
                if (hl_iter != hl.end()) {
64✔
663
                    hl_iter->second.annotate(comment_line,
16✔
664
                                             line_range{(int) filename_width});
16✔
665
                }
666
            }
64✔
667

668
            dst.emplace_back(comment_line);
64✔
669
        }
670
    }
56✔
671
    if (!line_meta.bm_tags.empty()) {
254✔
672
        attr_line_t al;
67✔
673

674
        al.with_string(" \u2514");
67✔
675
        for (const auto& str : line_meta.bm_tags) {
134✔
676
            al.append(1, ' ').append(str,
134✔
677
                                     VC_STYLE.value(vc.attrs_for_ident(str)));
134✔
678
        }
679

680
        al.insert(0, filename_width, ' ');
67✔
681
        if (tc != nullptr) {
67✔
682
            auto hl = tc->get_highlights();
67✔
683
            auto hl_iter = hl.find({highlight_source_t::PREVIEW, "search"});
67✔
684

685
            if (hl_iter != hl.end()) {
67✔
686
                hl_iter->second.annotate(al, line_range{(int) filename_width});
14✔
687
            }
688
        }
67✔
689
        dst.emplace_back(al);
67✔
690
    }
67✔
691
    if (!line_meta.bm_annotations.la_pairs.empty()) {
254✔
692
        for (const auto& anno_pair : line_meta.bm_annotations.la_pairs) {
42✔
693
            attr_line_t al;
21✔
694
            md2attr_line mdal;
21✔
695

696
            mdal.add_lnav_script_icons();
21✔
697
            dst.push_back(
21✔
698
                attr_line_t()
21✔
699
                    .append(filename_width, ' ')
21✔
700
                    .appendf(FMT_STRING(" \u251c {}:"), anno_pair.first)
84✔
701
                    .with_attr_for_all(VC_ROLE.value(role_t::VCR_COMMENT)));
21✔
702

703
            auto parse_res = md4cpp::parse(anno_pair.second, mdal);
21✔
704
            if (parse_res.isOk()) {
21✔
705
                al.append(parse_res.unwrap());
21✔
706
            } else {
UNCOV
707
                log_error("%d: cannot convert annotation to markdown: %s",
×
708
                          (int) row,
709
                          parse_res.unwrapErr().c_str());
UNCOV
710
                al.append(anno_pair.second);
×
711
            }
712

713
            auto anno_lines = al.rtrim().split_lines();
21✔
714
            if (anno_lines.back().empty()) {
21✔
UNCOV
715
                anno_lines.pop_back();
×
716
            }
717
            for (size_t lpc = 0; lpc < anno_lines.size(); lpc++) {
69✔
718
                auto& anno_line = anno_lines[lpc];
48✔
719

720
                if (lpc == 0 && anno_line.empty()) {
48✔
721
                    continue;
5✔
722
                }
723
                // anno_line.with_attr_for_all(VC_ROLE.value(role_t::VCR_COMMENT));
724
                anno_line.insert(0,
43✔
725
                                 lpc == anno_lines.size() - 1
43✔
726
                                     ? " \u2570 "_comment
43✔
727
                                     : " \u2502 "_comment);
22✔
728
                anno_line.insert(0, filename_width, ' ');
43✔
729
                if (tc != nullptr) {
43✔
730
                    auto hl = tc->get_highlights();
43✔
731
                    auto hl_iter
732
                        = hl.find({highlight_source_t::PREVIEW, "search"});
43✔
733

734
                    if (hl_iter != hl.end()) {
43✔
735
                        hl_iter->second.annotate(
×
736
                            anno_line, line_range{(int) filename_width});
×
737
                    }
738
                }
43✔
739

740
                dst.emplace_back(anno_line);
43✔
741
            }
742
        }
21✔
743
    }
744
}
254✔
745

746
void
747
field_overlay_source::add_key_line_attrs(int key_size, bool last_line)
8✔
748
{
749
    auto& sa = this->fos_lines.back().get_attrs();
8✔
750
    struct line_range lr(1, 2);
8✔
751

752
    auto graphic = (last_line ? NCACS_LLCORNER : NCACS_LTEE);
8✔
753
    sa.emplace_back(lr, VC_GRAPHIC.value(graphic));
8✔
754

755
    lr.lr_start = 3 + key_size + 3;
8✔
756
    lr.lr_end = -1;
8✔
757
    sa.emplace_back(lr, VC_STYLE.value(text_attrs::with_bold()));
8✔
758
}
8✔
759

760
void
761
field_overlay_source::list_value_for_overlay(
18,087✔
762
    const listview_curses& lv,
763
    vis_line_t row,
764
    std::vector<attr_line_t>& value_out)
765
{
766
    // log_debug("value for overlay %d", row);
767
    if (row == lv.get_selection()) {
18,087✔
768
        this->build_field_lines(lv, row);
2,014✔
769
        value_out = this->fos_lines;
2,014✔
770
    }
771
    this->build_meta_line(lv, value_out, row);
18,087✔
772
}
18,087✔
773

774
bool
775
field_overlay_source::list_static_overlay(const listview_curses& lv,
157✔
776
                                          int y,
777
                                          int bottom,
778
                                          attr_line_t& value_out)
779
{
780
    const std::vector<attr_line_t>* lines = nullptr;
157✔
781
    if (this->fos_lss.text_line_count() == 0) {
157✔
UNCOV
782
        if (this->fos_lss.is_indexing_in_progress()
×
UNCOV
783
            || this->fos_lss.is_rebuild_forced())
×
784
        {
785
            auto msg = lnav::console::user_message::info(
UNCOV
786
                "Log messages are being indexed...");
×
UNCOV
787
            this->fos_static_lines = msg.to_attr_line().split_lines();
×
UNCOV
788
            this->fos_static_lines_state.clear();
×
UNCOV
789
            lines = &this->fos_static_lines;
×
UNCOV
790
        } else if (this->fos_lss.file_count() > 0) {
×
UNCOV
791
            hasher h;
×
UNCOV
792
            this->fos_lss.update_filter_hash_state(h);
×
UNCOV
793
            auto curr_state = h.to_array();
×
UNCOV
794
            if (this->fos_static_lines.empty()
×
UNCOV
795
                || curr_state != this->fos_static_lines_state)
×
796
            {
797
                auto msg = lnav::console::user_message::info(
UNCOV
798
                    "All log messages are currently hidden");
×
UNCOV
799
                auto hidden_file_count = size_t{0};
×
UNCOV
800
                for (const auto& ld : this->fos_lss) {
×
UNCOV
801
                    if (ld->get_file_ptr() == nullptr) {
×
UNCOV
802
                        continue;
×
803
                    }
UNCOV
804
                    if (!ld->is_visible()) {
×
UNCOV
805
                        hidden_file_count += 1;
×
806
                    }
807
                }
UNCOV
808
                if (hidden_file_count > 0) {
×
UNCOV
809
                    msg.with_note(attr_line_t()
×
UNCOV
810
                                      .append(lnav::roles::number(
×
UNCOV
811
                                          fmt::to_string(hidden_file_count)))
×
UNCOV
812
                                      .append(" file(s) are hidden"));
×
813
                }
UNCOV
814
                auto min_time = this->fos_lss.get_min_row_time();
×
UNCOV
815
                if (min_time) {
×
UNCOV
816
                    msg.with_note(attr_line_t("Logs before ")
×
UNCOV
817
                                      .append_quoted(lnav::to_rfc3339_string(
×
UNCOV
818
                                          min_time.value()))
×
UNCOV
819
                                      .append(" are not being shown"));
×
820
                }
UNCOV
821
                auto max_time = this->fos_lss.get_max_row_time();
×
UNCOV
822
                if (max_time) {
×
UNCOV
823
                    msg.with_note(attr_line_t("Logs after ")
×
UNCOV
824
                                      .append_quoted(lnav::to_rfc3339_string(
×
UNCOV
825
                                          max_time.value()))
×
UNCOV
826
                                      .append(" are not being shown"));
×
827
                }
UNCOV
828
                if (this->fos_lss.get_min_log_level()
×
UNCOV
829
                    > log_level_t::LEVEL_UNKNOWN)
×
830
                {
UNCOV
831
                    msg.with_note(
×
UNCOV
832
                        attr_line_t("Logs with a level below ")
×
UNCOV
833
                            .append_quoted(
×
UNCOV
834
                                level_names[this->fos_lss.get_min_log_level()])
×
UNCOV
835
                            .append(" are not being shown"));
×
836
                }
UNCOV
837
                auto& fs = this->fos_lss.get_filters();
×
UNCOV
838
                for (const auto& filt : fs) {
×
UNCOV
839
                    auto hits = this->fos_lss.get_filtered_count_for(
×
840
                        filt->get_index());
UNCOV
841
                    if (filt->get_type() == text_filter::EXCLUDE && hits == 0) {
×
UNCOV
842
                        continue;
×
843
                    }
UNCOV
844
                    auto cmd = attr_line_t(":" + filt->to_command());
×
UNCOV
845
                    readline_command_highlighter(cmd, std::nullopt);
×
UNCOV
846
                    msg.with_note(
×
UNCOV
847
                        attr_line_t("Filter ")
×
UNCOV
848
                            .append_quoted(cmd)
×
UNCOV
849
                            .append(" matched ")
×
UNCOV
850
                            .append(lnav::roles::number(fmt::to_string(hits)))
×
UNCOV
851
                            .append(" message(s) "));
×
852
                }
UNCOV
853
                if (this->fos_lss.get_marked_only()) {
×
UNCOV
854
                    msg.with_note(attr_line_t("The ")
×
UNCOV
855
                                      .append_quoted(lnav::roles::keyword(
×
856
                                          ":hide-unmarked-lines"))
UNCOV
857
                                      .append(" command was used and no "
×
858
                                              "unfiltered lines are marked"));
859
                }
UNCOV
860
                this->fos_static_lines = msg.to_attr_line().split_lines();
×
UNCOV
861
                this->fos_static_lines_state = curr_state;
×
862
            }
863

UNCOV
864
            lines = &this->fos_static_lines;
×
UNCOV
865
        } else if (this->fos_tss.empty()) {
×
UNCOV
866
            lines = lnav::messages::view::no_files();
×
867
        } else {
UNCOV
868
            lines = lnav::messages::view::only_text_files();
×
869
        }
870
    } else {
871
        this->fos_static_lines.clear();
157✔
872
    }
873

874
    if (lines != nullptr && y < (ssize_t) lines->size()) {
157✔
UNCOV
875
        value_out = lines->at(y);
×
UNCOV
876
        value_out.with_attr_for_all(VC_ROLE.value(role_t::VCR_STATUS));
×
UNCOV
877
        if (y == (ssize_t) lines->size() - 1) {
×
UNCOV
878
            value_out.with_attr_for_all(
×
UNCOV
879
                VC_STYLE.value(text_attrs::with_underline()));
×
880
        }
UNCOV
881
        return true;
×
882
    }
883

884
    return false;
157✔
885
}
886

887
std::optional<attr_line_t>
UNCOV
888
field_overlay_source::list_header_for_overlay(const listview_curses& lv,
×
889
                                              vis_line_t vl)
890
{
UNCOV
891
    attr_line_t retval;
×
892

UNCOV
893
    retval.append(this->fos_lss.get_filename_offset(), ' ');
×
UNCOV
894
    if (this->fos_contexts.top().c_show) {
×
895
        retval
UNCOV
896
            .appendf(FMT_STRING("\u258C Line {:L} parser details.  "
×
897
                                "Press "),
UNCOV
898
                     (int) vl)
×
UNCOV
899
            .append("p"_hotkey)
×
UNCOV
900
            .append(" to hide this panel.");
×
901
    } else {
UNCOV
902
        retval.append("\u258C Line ")
×
UNCOV
903
            .append(
×
UNCOV
904
                lnav::roles::number(fmt::format(FMT_STRING("{:L}"), (int) vl)))
×
UNCOV
905
            .append(" metadata");
×
906
    }
907

UNCOV
908
    if (lv.get_overlay_selection()) {
×
UNCOV
909
        retval.append("  ")
×
UNCOV
910
            .append("SPC"_hotkey)
×
UNCOV
911
            .append(": hide/show field  ")
×
UNCOV
912
            .append("c"_hotkey)
×
UNCOV
913
            .append(": copy field value  ")
×
UNCOV
914
            .append("Esc"_hotkey)
×
UNCOV
915
            .append(": exit this panel");
×
916
    } else {
UNCOV
917
        retval.append("  Press ")
×
UNCOV
918
            .append("CTRL-]"_hotkey)
×
UNCOV
919
            .append(" to focus on this panel");
×
920
    }
921

UNCOV
922
    return retval;
×
923
}
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