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

tstack / lnav / 19080893927-2636

04 Nov 2025 07:43PM UTC coverage: 68.931% (+0.03%) from 68.898%
19080893927-2636

push

github

tstack
[tidy] some more cleanup

9 of 29 new or added lines in 5 files covered. (31.03%)

1855 existing lines in 27 files now uncovered.

50572 of 73366 relevant lines covered (68.93%)

428466.05 hits per line

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

72.55
/src/log.annotate.cc
1
/**
2
 * Copyright (c) 2023, 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 <future>
31

32
#include "log.annotate.hh"
33

34
#include "base/auto_fd.hh"
35
#include "base/auto_pid.hh"
36
#include "base/fs_util.hh"
37
#include "base/map_util.hh"
38
#include "base/paths.hh"
39
#include "line_buffer.hh"
40
#include "lnav.hh"
41
#include "log.annotate.cfg.hh"
42
#include "log_data_helper.hh"
43
#include "md4cpp.hh"
44
#include "readline_highlighters.hh"
45
#include "yajlpp/yajlpp.hh"
46

47
namespace lnav::log::annotate {
48

49
struct compiled_cond_expr {
50
    auto_mem<sqlite3_stmt> cce_stmt{sqlite3_finalize};
51
    bool cce_enabled{true};
52
};
53

54
struct expressions : lnav_config_listener {
55
    expressions() : lnav_config_listener(__FILE__) {}
1,148✔
56

57
    void reload_config(error_reporter& reporter) override
643✔
58
    {
59
        auto& lnav_db = injector::get<auto_sqlite3&>();
643✔
60

61
        if (lnav_db.in() == nullptr) {
643✔
62
            log_warning("db not initialized yet!");
×
UNCOV
63
            return;
×
64
        }
65

66
        const auto& cfg = injector::get<const config&>();
643✔
67

68
        this->e_cond_exprs.clear();
643✔
69
        for (const auto& pair : cfg.a_definitions) {
1,981✔
70
            if (pair.second.a_handler.pp_value.empty()) {
1,338✔
71
                auto um
72
                    = lnav::console::user_message::error(
2✔
73
                          "no handler specified for annotation")
74
                          .with_reason("Every annotation requires a handler")
2✔
75
                          .move();
1✔
76
                reporter(&pair.second.a_handler, um);
1✔
77
                continue;
1✔
78
            }
1✔
79

80
            auto stmt_str = fmt::format(FMT_STRING("SELECT 1 WHERE {}"),
2,674✔
81
                                        pair.second.a_condition);
1,337✔
82
            compiled_cond_expr cce;
1,337✔
83

84
            log_info("preparing annotation condition expression: %s",
1,337✔
85
                     stmt_str.c_str());
86
            auto retcode = sqlite3_prepare_v2(lnav_db,
2,674✔
87
                                              stmt_str.c_str(),
88
                                              stmt_str.size(),
1,337✔
89
                                              cce.cce_stmt.out(),
90
                                              nullptr);
91
            if (retcode != SQLITE_OK) {
1,337✔
92
                auto sql_al = attr_line_t(pair.second.a_condition)
2✔
93
                                  .with_attr_for_all(SA_PREFORMATTED.value())
2✔
94
                                  .with_attr_for_all(
1✔
95
                                      VC_ROLE.value(role_t::VCR_QUOTED_CODE))
2✔
96
                                  .move();
1✔
97
                readline_sql_highlighter(
1✔
98
                    sql_al, lnav::sql::dialect::sqlite, std::nullopt);
99
                intern_string_t cond_expr_path = intern_string::lookup(
100
                    fmt::format(FMT_STRING("/log/annotations/{}/condition"),
3✔
101
                                pair.first));
2✔
102
                auto snippet = lnav::console::snippet::from(
103
                    source_location(cond_expr_path), sql_al);
1✔
104

105
                auto um = lnav::console::user_message::error(
2✔
106
                              "SQL expression is invalid")
107
                              .with_reason(sqlite3_errmsg(lnav_db))
2✔
108
                              .with_snippet(snippet)
1✔
109
                              .move();
1✔
110

111
                reporter(&pair.second.a_condition, um);
1✔
112
                continue;
1✔
113
            }
1✔
114

115
            this->e_cond_exprs.insert(pair.first, std::move(cce));
1,336✔
116
        }
1,338✔
117
    }
118

119
    void unload_config() override { this->e_cond_exprs.clear(); }
738✔
120

121
    lnav::map::small<intern_string_t, compiled_cond_expr> e_cond_exprs;
122
};
123

124
static expressions exprs;
125

126
std::vector<intern_string_t>
127
applicable(vis_line_t vl)
6✔
128
{
129
    std::vector<intern_string_t> retval;
6✔
130
    auto& lss = lnav_data.ld_log_source;
6✔
131
    auto cl = lss.at(vl);
6✔
132
    auto ld = lss.find_data(cl);
6✔
133
    log_data_helper ldh(lss);
6✔
134

135
    ldh.load_line(vl, true);
6✔
136
    for (auto expr : exprs.e_cond_exprs) {
22✔
137
        if (!expr.second.cce_enabled) {
16✔
UNCOV
138
            continue;
×
139
        }
140

141
        auto eval_res
142
            = lss.eval_sql_filter(expr.second.cce_stmt.in(), ld, ldh.ldh_line);
16✔
143

144
        if (eval_res.isErr()) {
16✔
UNCOV
145
            log_error("eval failed: %s",
×
146
                      eval_res.unwrapErr().to_attr_line().get_string().c_str());
UNCOV
147
            expr.second.cce_enabled = false;
×
148
        } else {
149
            if (eval_res.unwrap()) {
16✔
150
                retval.emplace_back(expr.first);
3✔
151
            }
152
        }
153
    }
16✔
154
    return retval;
12✔
155
}
6✔
156

157
Result<void, lnav::console::user_message>
158
apply(vis_line_t vl, std::vector<intern_string_t> annos)
3✔
159
{
160
    const auto& cfg = injector::get<const config&>();
3✔
161
    auto& lss = lnav_data.ld_log_source;
3✔
162
    auto cl = lss.at(vl);
3✔
163
    auto ld = lss.find_data(cl);
3✔
164
    auto lf = (*ld)->get_file();
3✔
165
    logmsg_annotations la;
3✔
166
    log_data_helper ldh(lss);
3✔
167

168
    if (!ldh.load_line(vl, true)) {
3✔
169
        log_error("failed to parse line %d", vl);
×
UNCOV
170
        return Err(lnav::console::user_message::error("Failed to parse line"));
×
171
    }
172
    auto line_number = content_line_t{ldh.ldh_line_index - ldh.ldh_y_offset};
3✔
173
    lss.set_user_mark(&textview_curses::BM_META,
3✔
174
                      content_line_t{ldh.ldh_source_line - ldh.ldh_y_offset});
3✔
175

176
    yajlpp_gen gen;
3✔
177

178
    {
179
        auto bm_opt = lss.find_bookmark_metadata(vl);
3✔
180
        yajlpp_map root(gen);
3✔
181

182
        root.gen("log_line");
3✔
183
        root.gen((int64_t) vl);
3✔
184
        root.gen("log_tags");
3✔
185
        {
186
            yajlpp_array tag_array(gen);
3✔
187

188
            if (bm_opt) {
3✔
UNCOV
189
                const auto& bm = *(bm_opt.value());
×
190

191
                for (const auto& tag : bm.bm_tags) {
×
UNCOV
192
                    tag_array.gen(tag);
×
193
                }
194
            }
195
        }
3✔
196
        root.gen("log_path");
3✔
197
        root.gen(lf->get_filename().native());
3✔
198
        root.gen("log_format");
3✔
199
        root.gen(lf->get_format_name());
3✔
200
        root.gen("log_format_regex");
3✔
201
        root.gen(lf->get_format()->get_pattern_name(line_number));
3✔
202
        root.gen("log_msg");
3✔
203
        root.gen(ldh.ldh_line_values.lvv_sbr.to_string_fragment());
3✔
204
        for (const auto& val : ldh.ldh_line_values.lvv_values) {
33✔
205
            root.gen(val.lv_meta.lvm_name);
30✔
206
            switch (val.lv_meta.lvm_kind) {
30✔
207
                case value_kind_t::VALUE_NULL:
13✔
208
                    root.gen();
13✔
209
                    break;
13✔
210
                case value_kind_t::VALUE_INTEGER:
2✔
211
                    root.gen(val.lv_value.i);
2✔
212
                    break;
2✔
213
                case value_kind_t::VALUE_FLOAT:
×
214
                    root.gen(val.lv_value.d);
×
215
                    break;
×
216
                case value_kind_t::VALUE_BOOLEAN:
×
217
                    root.gen(val.lv_value.i ? true : false);
×
UNCOV
218
                    break;
×
219
                default:
15✔
220
                    root.gen(val.to_string());
15✔
221
                    break;
15✔
222
            }
223
        }
224
    }
3✔
225

226
    for (const auto& anno : annos) {
6✔
227
        auto iter = cfg.a_definitions.find(anno);
3✔
228
        if (iter == cfg.a_definitions.end()) {
3✔
UNCOV
229
            log_error("unknown annotation: %s", anno.c_str());
×
230
            continue;
2✔
231
        }
232

233
        if (startswith(iter->second.a_handler.pp_value, "|")) {
3✔
234
            intern_string_t handler_path = intern_string::lookup(
235
                fmt::format(FMT_STRING("/log/annotations/{}/handler"), anno));
8✔
236
            logline_value_vector values;
2✔
237
            exec_context ec(&values, internal_sql_callback, pipe_callback);
2✔
238
            db_label_source anno_label_source;
2✔
239

240
            ec.with_perms(exec_context::perm_t::READ_ONLY);
2✔
241
            ec.ec_local_vars.push(std::map<std::string, scoped_value_t>());
2✔
242
            ec.ec_top_line = vl;
2✔
243
            auto src_guard = ec.enter_db_source(&anno_label_source);
2✔
244
            auto src_loc = source_location{handler_path};
2✔
245

246
            auto exec_res
247
                = ec.execute(src_loc, iter->second.a_handler.pp_value);
2✔
248
            if (exec_res.isErr()) {
2✔
UNCOV
249
                auto err_msg = exec_res.unwrapErr();
×
250

251
                la.la_pairs[anno.to_string()]
×
252
                    = err_msg.to_attr_line().al_string;
×
UNCOV
253
            } else {
×
254
                auto content = exec_res.unwrap();
2✔
255
                la.la_pairs[anno.to_string()] = content;
2✔
256
            }
2✔
257

258
            lnav_data.ld_views[LNV_LOG].reload_data();
2✔
259
            lnav_data.ld_views[LNV_LOG].set_needs_update();
2✔
260
            continue;
2✔
261
        }
2✔
262

263
        la.la_pairs[anno.to_string()] = "Loading...";
1✔
264
        auto child_fds_res = auto_pipe::for_child_fds(
265
            STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO);
1✔
266
        if (child_fds_res.isErr()) {
1✔
267
            auto um
268
                = lnav::console::user_message::error("unable to create pipes")
×
269
                      .with_reason(child_fds_res.unwrapErr())
×
270
                      .move();
×
UNCOV
271
            return Err(um);
×
272
        }
273

274
        auto child_res = lnav::pid::from_fork();
1✔
275
        if (child_res.isErr()) {
1✔
276
            auto um
277
                = lnav::console::user_message::error("unable to fork() child")
×
278
                      .with_reason(child_res.unwrapErr())
×
279
                      .move();
×
UNCOV
280
            return Err(um);
×
281
        }
282

283
        auto child_fds = child_fds_res.unwrap();
1✔
284

285
        auto child = child_res.unwrap();
1✔
286
        for (auto& child_fd : child_fds) {
4✔
287
            child_fd.after_fork(child.in());
3✔
288
        }
289
        if (child.in_child()) {
1✔
290
            const char* exec_args[] = {
×
UNCOV
291
                getenv_opt("SHELL").value_or("bash"),
×
292
                "-c",
UNCOV
293
                iter->second.a_handler.pp_value.c_str(),
×
294
                nullptr,
295
            };
296

UNCOV
297
            std::vector<std::filesystem::path> path_v;
×
298

299
            auto src_path
300
                = std::filesystem::path(
×
301
                      iter->second.a_handler.pp_location.sl_source.to_string())
×
302
                      .parent_path();
×
303
            path_v.push_back(src_path);
×
304
            path_v.push_back(lnav::paths::dotlnav() / "formats/default");
×
UNCOV
305
            auto path_var = lnav::filesystem::build_path(path_v);
×
306

307
            log_debug("annotate PATH: %s", path_var.c_str());
×
308
            setenv("PATH", path_var.c_str(), 1);
×
309
            execvp(exec_args[0], (char**) exec_args);
×
310
            _exit(EXIT_FAILURE);
×
UNCOV
311
        }
×
312

313
        auto out_reader = std::async(
314
            std::launch::async,
315
            [out_fd = std::move(child_fds[1].read_end())]() mutable {
2✔
316
                std::string retval;
1✔
317
                file_range last_range;
1✔
318
                line_buffer lb;
1✔
319

320
                lb.set_fd(out_fd);
1✔
321
                while (true) {
322
                    auto load_res = lb.load_next_line(last_range);
2✔
323
                    if (load_res.isErr()) {
2✔
UNCOV
324
                        log_error("unable to load next line: %s",
×
325
                                  load_res.unwrapErr().c_str());
UNCOV
326
                        break;
×
327
                    }
328

329
                    auto li = load_res.unwrap();
2✔
330
                    if (li.li_file_range.empty()) {
2✔
331
                        break;
1✔
332
                    }
333
                    auto read_res = lb.read_range(li.li_file_range);
1✔
334
                    if (read_res.isErr()) {
1✔
UNCOV
335
                        log_error("unable to read next line: %s",
×
336
                                  load_res.unwrapErr().c_str());
UNCOV
337
                        break;
×
338
                    }
339

340
                    auto sbr = read_res.unwrap();
1✔
341
                    retval.append(sbr.get_data(), sbr.length());
1✔
342

343
                    last_range = li.li_file_range;
1✔
344
                }
3✔
345

346
                return retval;
2✔
347
            });
2✔
348

349
        auto err_reader = std::async(
350
            std::launch::async,
351
            [err_fd = std::move(child_fds[2].read_end()),
2✔
352
             handler = iter->second.a_handler.pp_value]() mutable {
1✔
353
                std::string retval;
1✔
354
                file_range last_range;
1✔
355
                line_buffer lb;
1✔
356

357
                lb.set_fd(err_fd);
1✔
358
                while (true) {
359
                    auto load_res = lb.load_next_line(last_range);
1✔
360
                    if (load_res.isErr()) {
1✔
UNCOV
361
                        log_error("unable to load next line: %s",
×
362
                                  load_res.unwrapErr().c_str());
UNCOV
363
                        break;
×
364
                    }
365

366
                    auto li = load_res.unwrap();
1✔
367
                    if (li.li_file_range.empty()) {
1✔
368
                        break;
1✔
369
                    }
370
                    auto read_res = lb.read_range(li.li_file_range);
×
371
                    if (read_res.isErr()) {
×
UNCOV
372
                        log_error("unable to read next line: %s",
×
373
                                  load_res.unwrapErr().c_str());
UNCOV
374
                        break;
×
375
                    }
376

377
                    auto sbr = read_res.unwrap();
×
378
                    retval.append(sbr.get_data(), sbr.length());
×
379
                    sbr.rtrim(is_line_ending);
×
UNCOV
380
                    log_debug("%s: %.*s",
×
381
                              handler.c_str(),
382
                              sbr.length(),
383
                              sbr.get_data());
384

UNCOV
385
                    last_range = li.li_file_range;
×
386
                }
1✔
387

388
                return retval;
2✔
389
            });
2✔
390

391
        auto write_res
392
            = child_fds[0].write_end().write_fully(gen.to_string_fragment());
1✔
393
        if (write_res.isErr()) {
1✔
UNCOV
394
            log_error("bah %s", write_res.unwrapErr().c_str());
×
395
        }
396
        child_fds[0].write_end().reset();
1✔
397
        auto finalizer = [anno,
1✔
398
                          out_reader1 = out_reader.share(),
399
                          err_reader1 = err_reader.share(),
400
                          lf,
401
                          line_number,
402
                          handler = iter->second.a_handler.pp_value](
2✔
403
                             auto& fc,
404
                             auto_pid<process_state::finished>& child) mutable {
405
            auto& line_anno
1✔
406
                = lf->get_bookmark_metadata()[line_number].bm_annotations;
1✔
407
            auto content = out_reader1.get();
1✔
408
            if (!child.was_normal_exit()) {
1✔
409
                content.append(fmt::format(
×
UNCOV
410
                    FMT_STRING(
×
411
                        "\n\n\u2718 annotation handler \u201c{}\u201d failed "
412
                        "with signal {}:\n\n<pre>\n{}\n</pre>\n"),
413
                    handler,
×
UNCOV
414
                    child.term_signal(),
×
415
                    err_reader1.get()));
416
            } else if (child.exit_status() != 0) {
1✔
417
                auto escaped = md4cpp::escape_html(err_reader1.get());
×
418
                auto escaped_sf = to_string_fragment(escaped.inner);
×
419
                content.append(fmt::format(
×
UNCOV
420
                    FMT_STRING(
×
421
                        "\n\n<span "
422
                        "class=\"-lnav_log-level-styles_error\">"
423
                        "\u2718 annotation handler \u201c{}\u201d exited "
424
                        "with status {}:</span>\n\n<pre>{}</pre>"),
425
                    handler,
×
UNCOV
426
                    child.exit_status(),
×
427
                    escaped_sf));
428
            }
429
            line_anno.la_pairs[anno.to_string()] = content;
1✔
430
            lnav_data.ld_views[LNV_LOG].reload_data();
1✔
431
            lnav_data.ld_views[LNV_LOG].set_needs_update();
1✔
432
        };
2✔
433

434
        lnav_data.ld_child_pollers.emplace_back(
1✔
435
            (*ld)->get_file_ptr()->get_filename(),
1✔
436
            std::move(child),
1✔
437
            std::move(finalizer));
1✔
438
    }
1✔
439
    lf->get_bookmark_metadata()[line_number].bm_annotations = la;
3✔
440
    return Ok();
3✔
441
}
3✔
442

443
}  // namespace lnav::log::annotate
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