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

tstack / lnav / 19153812334-2653

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

push

github

tstack
[tidy] add format-string warnings

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

3 existing lines in 2 files now uncovered.

50702 of 73446 relevant lines covered (69.03%)

435988.05 hits per line

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

92.52
/src/log_format_loader.cc
1
/**
2
 * Copyright (c) 2013-2016, 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_loader.cc
30
 */
31

32
#include <map>
33
#include <string>
34

35
#include "log_format.hh"
36

37
#include <glob.h>
38
#include <libgen.h>
39
#include <sys/stat.h>
40

41
#include "base/auto_fd.hh"
42
#include "base/from_trait.hh"
43
#include "base/fs_util.hh"
44
#include "base/paths.hh"
45
#include "base/string_util.hh"
46
#include "bin2c.hh"
47
#include "builtin-scripts.h"
48
#include "builtin-sh-scripts.h"
49
#include "config.h"
50
#include "default-formats.h"
51
#include "file_format.hh"
52
#include "fmt/format.h"
53
#include "format.scripts.hh"
54
#include "lnav_config.hh"
55
#include "log_format.hh"
56
#include "log_format_ext.hh"
57
#include "log_format_loader.hh"
58
#include "sql_execute.hh"
59
#include "sql_util.hh"
60
#include "yajlpp/yajlpp.hh"
61
#include "yajlpp/yajlpp_def.hh"
62

63
static void extract_metadata(string_fragment, struct script_metadata& meta_out);
64

65
using log_formats_map_t
66
    = std::map<intern_string_t, std::shared_ptr<external_log_format>>;
67

68
using namespace lnav::roles::literals;
69

70
static auto intern_lifetime = intern_string::get_table_lifetime();
71
static log_formats_map_t LOG_FORMATS;
72

73
struct loader_userdata {
74
    yajlpp_parse_context* ud_parse_context{nullptr};
75
    std::string ud_file_schema;
76
    std::filesystem::path ud_format_path;
77
    std::vector<intern_string_t>* ud_format_names{nullptr};
78
    std::vector<lnav::console::user_message>* ud_errors{nullptr};
79
};
80

81
static external_log_format*
82
ensure_format(const yajlpp_provider_context& ypc, loader_userdata* ud)
3,744,882✔
83
{
84
    const intern_string_t name = ypc.get_substr_i(0);
3,744,882✔
85
    auto* formats = ud->ud_format_names;
3,744,882✔
86
    auto* retval = LOG_FORMATS[name].get();
3,744,882✔
87
    if (retval == nullptr) {
3,744,882✔
88
        LOG_FORMATS[name] = std::make_shared<external_log_format>(name);
50,716✔
89
        retval = LOG_FORMATS[name].get();
50,716✔
90
        log_debug("Loading format -- %s", name.get());
50,716✔
91
    }
92
    retval->elf_source_path.insert(ud->ud_format_path.parent_path().string());
3,744,882✔
93

94
    if (find(formats->begin(), formats->end(), name) == formats->end()) {
3,744,882✔
95
        formats->push_back(name);
50,812✔
96
    }
97

98
    if (!ud->ud_format_path.empty()) {
3,744,882✔
99
        const intern_string_t i_src_path
100
            = intern_string::lookup(ud->ud_format_path.string());
40,623✔
101
        auto srcs_iter = retval->elf_format_sources.find(i_src_path);
40,623✔
102
        if (srcs_iter == retval->elf_format_sources.end()) {
40,623✔
103
            retval->elf_format_source_order.emplace_back(ud->ud_format_path);
1,165✔
104
            retval->elf_format_sources[i_src_path]
2,330✔
105
                = ud->ud_parse_context->get_line_number();
1,165✔
106
        }
107
    }
108

109
    if (ud->ud_format_path.empty()) {
3,744,882✔
110
        retval->elf_builtin_format = true;
3,704,259✔
111
    }
112

113
    return retval;
3,744,882✔
114
}
115

116
static external_log_format::pattern*
117
pattern_provider(const yajlpp_provider_context& ypc, external_log_format* elf)
295,200✔
118
{
119
    auto regex_name = ypc.get_substr(0);
295,200✔
120
    auto& pat = elf->elf_patterns[regex_name];
295,200✔
121

122
    if (pat.get() == nullptr) {
295,200✔
123
        pat = std::make_shared<external_log_format::pattern>();
97,659✔
124
    }
125

126
    if (pat->p_config_path.empty()) {
295,200✔
127
        pat->p_name = intern_string::lookup(regex_name);
97,659✔
128
        pat->p_config_path = fmt::format(
195,318✔
129
            FMT_STRING("/{}/regex/{}"), elf->get_name(), regex_name);
488,295✔
130
    }
131

132
    return pat.get();
590,400✔
133
}
295,200✔
134

135
static external_log_format::value_def*
136
value_def_provider(const yajlpp_provider_context& ypc, external_log_format* elf)
1,653,442✔
137
{
138
    const intern_string_t value_name = ypc.get_substr_i(0);
1,653,442✔
139

140
    auto iter = elf->elf_value_defs.find(value_name);
1,653,442✔
141
    std::shared_ptr<external_log_format::value_def> retval;
1,653,442✔
142

143
    if (iter == elf->elf_value_defs.end()) {
1,653,442✔
144
        retval = std::make_shared<external_log_format::value_def>(
423,696✔
145
            value_name,
146
            value_kind_t::VALUE_TEXT,
423,696✔
147
            logline_value_meta::external_column{},
×
148
            elf);
423,696✔
149
        elf->elf_value_defs[value_name] = retval;
423,696✔
150
        elf->elf_value_def_order.emplace_back(retval);
423,696✔
151
    } else {
152
        retval = iter->second;
1,229,746✔
153
    }
154

155
    return retval.get();
3,306,884✔
156
}
1,653,442✔
157

158
static format_tag_def*
159
format_tag_def_provider(const yajlpp_provider_context& ypc,
12,896✔
160
                        external_log_format* elf)
161
{
162
    const intern_string_t tag_name = ypc.get_substr_i(0);
12,896✔
163

164
    auto iter = elf->lf_tag_defs.find(tag_name);
12,896✔
165
    std::shared_ptr<format_tag_def> retval;
12,896✔
166

167
    if (iter == elf->lf_tag_defs.end()) {
12,896✔
168
        auto tag_with_hash = fmt::format(FMT_STRING("#{}"), tag_name);
6,966✔
169
        retval = std::make_shared<format_tag_def>(tag_with_hash);
2,322✔
170
        elf->lf_tag_defs[tag_name] = retval;
2,322✔
171
    } else {
2,322✔
172
        retval = iter->second;
10,574✔
173
    }
174

175
    return retval.get();
25,792✔
176
}
12,896✔
177

178
static format_partition_def*
179
format_partition_def_provider(const yajlpp_provider_context& ypc,
7,053✔
180
                              external_log_format* elf)
181
{
182
    const intern_string_t partition_name = ypc.get_substr_i(0);
7,053✔
183

184
    auto iter = elf->lf_partition_defs.find(partition_name);
7,053✔
185
    std::shared_ptr<format_partition_def> retval;
7,053✔
186

187
    if (iter == elf->lf_partition_defs.end()) {
7,053✔
188
        retval = std::make_shared<format_partition_def>(
1,674✔
189
            partition_name.to_string());
2,511✔
190
        elf->lf_partition_defs[partition_name] = retval;
837✔
191
    } else {
192
        retval = iter->second;
6,216✔
193
    }
194

195
    return retval.get();
14,106✔
196
}
7,053✔
197

198
static scaling_factor*
199
scaling_factor_provider(const yajlpp_provider_context& ypc,
5,928✔
200
                        external_log_format::value_def* value_def)
201
{
202
    auto scale_name = ypc.get_substr_i(0);
5,928✔
203
    auto& retval = value_def->vd_unit_scaling[scale_name];
5,928✔
204

205
    return &retval;
5,928✔
206
}
207

208
static external_log_format::json_format_element&
209
ensure_json_format_element(external_log_format* elf, int index)
200,854✔
210
{
211
    elf->jlf_line_format.resize(index + 1);
200,854✔
212

213
    return elf->jlf_line_format[index];
200,854✔
214
}
215

216
static external_log_format::json_format_element*
217
line_format_provider(const yajlpp_provider_context& ypc,
165,031✔
218
                     external_log_format* elf)
219
{
220
    auto& jfe = ensure_json_format_element(elf, ypc.ypc_index);
165,031✔
221

222
    jfe.jfe_type = external_log_format::json_log_field::VARIABLE;
165,031✔
223

224
    return &jfe;
165,031✔
225
}
226

227
static int
228
read_format_bool(yajlpp_parse_context* ypc, int val)
3,545✔
229
{
230
    auto elf = (external_log_format*) ypc->ypc_obj_stack.top();
3,545✔
231
    auto field_name = ypc->get_path_fragment(1);
3,545✔
232

233
    if (field_name == "json" && val) {
3,545✔
234
        elf->elf_type = external_log_format::elf_type_t::ELF_TYPE_JSON;
2,804✔
235
    }
236

237
    return 1;
3,545✔
238
}
3,545✔
239

240
static int
241
read_format_double(yajlpp_parse_context* ypc, double val)
1✔
242
{
243
    auto elf = (external_log_format*) ypc->ypc_obj_stack.top();
1✔
244
    auto field_name = ypc->get_path_fragment(1);
1✔
245

246
    if (field_name == "timestamp-divisor") {
1✔
247
        if (val <= 0) {
1✔
248
            ypc->report_error(
2✔
249
                lnav::console::user_message::error(
×
250
                    attr_line_t()
2✔
251
                        .append_quoted(fmt::to_string(val))
2✔
252
                        .append(" is not a valid value for ")
1✔
253
                        .append_quoted(lnav::roles::symbol(
2✔
254
                            ypc->get_full_path().to_string())))
2✔
255
                    .with_reason("value cannot be less than or equal to zero")
2✔
256
                    .with_snippet(ypc->get_snippet())
2✔
257
                    .with_help(ypc->ypc_current_handler->get_help_text(ypc)));
1✔
258
        }
259
        elf->elf_timestamp_divisor = val;
1✔
260
    }
261

262
    return 1;
1✔
263
}
1✔
264

265
static int
266
read_format_int(yajlpp_parse_context* ypc, long long val)
837✔
267
{
268
    auto elf = (external_log_format*) ypc->ypc_obj_stack.top();
837✔
269
    auto field_name = ypc->get_path_fragment(1);
837✔
270

271
    if (field_name == "timestamp-divisor") {
837✔
272
        if (val <= 0) {
837✔
273
            ypc->report_error(
×
274
                lnav::console::user_message::error(
×
275
                    attr_line_t()
×
276
                        .append_quoted(fmt::to_string(val))
×
277
                        .append(" is not a valid value for ")
×
278
                        .append_quoted(lnav::roles::symbol(
×
279
                            ypc->get_full_path().to_string())))
×
280
                    .with_reason("value cannot be less than or equal to zero")
×
281
                    .with_snippet(ypc->get_snippet())
×
282
                    .with_help(ypc->ypc_current_handler->get_help_text(ypc)));
×
283
        }
284
        elf->elf_timestamp_divisor = val;
837✔
285
    }
286

287
    return 1;
837✔
288
}
837✔
289

290
static int
291
read_format_field(yajlpp_parse_context* ypc,
94,628✔
292
                  const unsigned char* str,
293
                  size_t len,
294
                  yajl_string_props_t*)
295
{
296
    auto elf = (external_log_format*) ypc->ypc_obj_stack.top();
94,628✔
297
    auto leading_slash = len > 0 && str[0] == '/';
94,628✔
298
    auto value = std::string((const char*) (leading_slash ? str + 1 : str),
299
                             leading_slash ? len - 1 : len);
94,628✔
300
    auto field_name = ypc->get_path_fragment(1);
94,628✔
301

302
    if (field_name == "timestamp-format") {
94,628✔
303
        elf->lf_timestamp_format.push_back(intern_string::lookup(value)->get());
9,088✔
304
    } else if (field_name == "module-field") {
85,540✔
305
        elf->elf_module_id_field = intern_string::lookup(value);
1,483✔
306
        elf->elf_container = true;
1,483✔
307
    }
308

309
    return 1;
94,628✔
310
}
94,628✔
311

312
static int
313
read_levels(yajlpp_parse_context* ypc,
83,172✔
314
            const unsigned char* str,
315
            size_t len,
316
            yajl_string_props_t*)
317
{
318
    auto elf = (external_log_format*) ypc->ypc_obj_stack.top();
83,172✔
319
    auto regex = std::string((const char*) str, len);
83,172✔
320
    auto level_name_or_number = ypc->get_path_fragment(2);
83,172✔
321
    log_level_t level = string2level(level_name_or_number.c_str());
83,172✔
322
    auto value_frag = string_fragment::from_bytes(str, len);
83,172✔
323

324
    elf->elf_level_patterns[level].lp_pcre.pp_path = ypc->get_full_path();
83,172✔
325
    auto compile_res = lnav::pcre2pp::code::from(value_frag);
83,172✔
326
    if (compile_res.isErr()) {
83,172✔
327
        static const intern_string_t PATTERN_SRC
328
            = intern_string::lookup("pattern");
3✔
329
        auto ce = compile_res.unwrapErr();
1✔
330
        ypc->ypc_current_handler->report_error(
1✔
331
            ypc,
332
            value_frag.to_string(),
2✔
333
            lnav::console::to_user_message(PATTERN_SRC, ce));
2✔
334
    } else {
1✔
335
        elf->elf_level_patterns[level].lp_pcre.pp_value
83,171✔
336
            = compile_res.unwrap().to_shared();
166,342✔
337
    }
338

339
    return 1;
83,172✔
340
}
83,172✔
341

342
static int
343
read_level_int(yajlpp_parse_context* ypc, long long val)
12,789✔
344
{
345
    auto elf = (external_log_format*) ypc->ypc_obj_stack.top();
12,789✔
346
    auto level_name_or_number = ypc->get_path_fragment(2);
12,789✔
347
    log_level_t level = string2level(level_name_or_number.c_str());
12,789✔
348

349
    elf->elf_level_pairs.emplace_back(val, level);
12,789✔
350

351
    return 1;
12,789✔
352
}
12,789✔
353

354
static int
355
read_action_def(yajlpp_parse_context* ypc,
741✔
356
                const unsigned char* str,
357
                size_t len,
358
                yajl_string_props_t*)
359
{
360
    auto elf = (external_log_format*) ypc->ypc_obj_stack.top();
741✔
361
    auto action_name = ypc->get_path_fragment(2);
741✔
362
    auto field_name = ypc->get_path_fragment(3);
741✔
363
    auto val = std::string((const char*) str, len);
741✔
364

365
    elf->lf_action_defs[action_name].ad_name = action_name;
741✔
366
    if (field_name == "label") {
741✔
367
        elf->lf_action_defs[action_name].ad_label = val;
741✔
368
    }
369

370
    return 1;
741✔
371
}
741✔
372

373
static int
374
read_action_bool(yajlpp_parse_context* ypc, int val)
741✔
375
{
376
    auto elf = (external_log_format*) ypc->ypc_obj_stack.top();
741✔
377
    auto action_name = ypc->get_path_fragment(2);
741✔
378
    auto field_name = ypc->get_path_fragment(3);
741✔
379

380
    elf->lf_action_defs[action_name].ad_capture_output = val;
741✔
381

382
    return 1;
741✔
383
}
741✔
384

385
static int
386
read_action_cmd(yajlpp_parse_context* ypc,
741✔
387
                const unsigned char* str,
388
                size_t len,
389
                yajl_string_props_t*)
390
{
391
    auto elf = (external_log_format*) ypc->ypc_obj_stack.top();
741✔
392
    auto action_name = ypc->get_path_fragment(2);
741✔
393
    auto field_name = ypc->get_path_fragment(3);
741✔
394
    auto val = std::string((const char*) str, len);
741✔
395

396
    elf->lf_action_defs[action_name].ad_name = action_name;
741✔
397
    elf->lf_action_defs[action_name].ad_cmdline.push_back(val);
741✔
398

399
    return 1;
741✔
400
}
741✔
401

402
static external_log_format::sample_t&
403
ensure_sample(external_log_format* elf, int index)
205,584✔
404
{
405
    elf->elf_samples.resize(index + 1);
205,584✔
406

407
    return elf->elf_samples[index];
205,584✔
408
}
409

410
static external_log_format::sample_t*
411
sample_provider(const yajlpp_provider_context& ypc, external_log_format* elf)
205,584✔
412
{
413
    auto& sample = ensure_sample(elf, ypc.ypc_index);
205,584✔
414

415
    return &sample;
205,584✔
416
}
417

418
static int
419
read_json_constant(yajlpp_parse_context* ypc,
35,823✔
420
                   const unsigned char* str,
421
                   size_t len,
422
                   yajl_string_props_t*)
423
{
424
    auto val = std::string((const char*) str, len);
35,823✔
425
    auto elf = (external_log_format*) ypc->ypc_obj_stack.top();
35,823✔
426

427
    ypc->ypc_array_index.back() += 1;
35,823✔
428
    auto& jfe = ensure_json_format_element(elf, ypc->ypc_array_index.back());
35,823✔
429
    jfe.jfe_type = external_log_format::json_log_field::CONSTANT;
35,823✔
430
    jfe.jfe_default_value = val;
35,823✔
431

432
    return 1;
35,823✔
433
}
35,823✔
434

435
static const struct json_path_container pattern_handlers = {
436
    yajlpp::property_handler("pattern")
437
        .with_synopsis("<message-regex>")
438
        .with_description(
439
            "The regular expression to match a log message and capture fields.")
440
        .with_min_length(1)
441
        .for_field(&external_log_format::pattern::p_pcre),
442
    yajlpp::property_handler("module-format")
443
        .with_synopsis("<bool>")
444
        .with_description(
445
            "If true, this pattern will only be used to parse message bodies "
446
            "of container formats, like syslog")
447
        .for_field(&external_log_format::pattern::p_module_format),
448
};
449

450
static constexpr json_path_handler_base::enum_value_t SUBSECOND_UNIT_ENUM[] = {
451
    {"milli"_frag, log_format::subsecond_unit::milli},
452
    {"micro"_frag, log_format::subsecond_unit::micro},
453
    {"nano"_frag, log_format::subsecond_unit::nano},
454

455
    json_path_handler_base::ENUM_TERMINATOR,
456
};
457

458
static constexpr json_path_handler_base::enum_value_t TS_POR_ENUM[] = {
459
    {"send"_frag, timestamp_point_of_reference_t::send},
460
    {"start"_frag, timestamp_point_of_reference_t::start},
461

462
    json_path_handler_base::ENUM_TERMINATOR,
463
};
464

465
static constexpr json_path_handler_base::enum_value_t ALIGN_ENUM[] = {
466
    {"left"_frag, external_log_format::json_format_element::align_t::LEFT},
467
    {"right"_frag, external_log_format::json_format_element::align_t::RIGHT},
468

469
    json_path_handler_base::ENUM_TERMINATOR,
470
};
471

472
static constexpr json_path_handler_base::enum_value_t OVERFLOW_ENUM[] = {
473
    {"abbrev"_frag,
474
     external_log_format::json_format_element::overflow_t::ABBREV},
475
    {"truncate"_frag,
476
     external_log_format::json_format_element::overflow_t::TRUNCATE},
477
    {"dot-dot"_frag,
478
     external_log_format::json_format_element::overflow_t::DOTDOT},
479
    {"last-word"_frag,
480
     external_log_format::json_format_element::overflow_t::LASTWORD},
481

482
    json_path_handler_base::ENUM_TERMINATOR,
483
};
484

485
static constexpr json_path_handler_base::enum_value_t TRANSFORM_ENUM[] = {
486
    {"none"_frag, external_log_format::json_format_element::transform_t::NONE},
487
    {"uppercase"_frag,
488
     external_log_format::json_format_element::transform_t::UPPERCASE},
489
    {"lowercase"_frag,
490
     external_log_format::json_format_element::transform_t::LOWERCASE},
491
    {"capitalize"_frag,
492
     external_log_format::json_format_element::transform_t::CAPITALIZE},
493

494
    json_path_handler_base::ENUM_TERMINATOR,
495
};
496

497
static const json_path_container line_format_handlers = {
498
    yajlpp::property_handler("field")
499
        .with_synopsis("<field-name>")
500
        .with_description(
501
            "The name of the field to substitute at this position")
502
        .for_field(&external_log_format::json_format_element::jfe_value),
503

504
    yajlpp::property_handler("default-value")
505
        .with_synopsis("<string>")
506
        .with_description(
507
            "The default value for this position if the field is null")
508
        .for_field(
509
            &external_log_format::json_format_element::jfe_default_value),
510

511
    yajlpp::property_handler("timestamp-format")
512
        .with_synopsis("<string>")
513
        .with_min_length(1)
514
        .with_description("The strftime(3) format for this field")
515
        .for_field(&external_log_format::json_format_element::jfe_ts_format),
516

517
    yajlpp::property_handler("min-width")
518
        .with_min_value(0)
519
        .with_synopsis("<size>")
520
        .with_description("The minimum width of the field")
521
        .for_field(&external_log_format::json_format_element::jfe_min_width),
522

523
    yajlpp::property_handler("auto-width")
524
        .with_description("Automatically detect the necessary width of the "
525
                          "field based on the observed values")
526
        .for_field(&external_log_format::json_format_element::jfe_auto_width),
527

528
    yajlpp::property_handler("max-width")
529
        .with_min_value(0)
530
        .with_synopsis("<size>")
531
        .with_description("The maximum width of the field")
532
        .for_field(&external_log_format::json_format_element::jfe_max_width),
533

534
    yajlpp::property_handler("align")
535
        .with_synopsis("left|right")
536
        .with_description(
537
            "Align the text in the column to the left or right side")
538
        .with_enum_values(ALIGN_ENUM)
539
        .for_field(&external_log_format::json_format_element::jfe_align),
540

541
    yajlpp::property_handler("overflow")
542
        .with_synopsis("abbrev|truncate|dot-dot")
543
        .with_description("Overflow style")
544
        .with_enum_values(OVERFLOW_ENUM)
545
        .for_field(&external_log_format::json_format_element::jfe_overflow),
546

547
    yajlpp::property_handler("text-transform")
548
        .with_synopsis("none|uppercase|lowercase|capitalize")
549
        .with_description("Text transformation")
550
        .with_enum_values(TRANSFORM_ENUM)
551
        .for_field(
552
            &external_log_format::json_format_element::jfe_text_transform),
553

554
    yajlpp::property_handler("prefix")
555
        .with_synopsis("<str>")
556
        .with_description("Text to prepend to the value")
557
        .for_field(&external_log_format::json_format_element::jfe_prefix),
558

559
    yajlpp::property_handler("suffix")
560
        .with_synopsis("<str>")
561
        .with_description("Text to append to the value")
562
        .for_field(&external_log_format::json_format_element::jfe_suffix),
563
};
564

565
static constexpr json_path_handler_base::enum_value_t KIND_ENUM[] = {
566
    {"string"_frag, value_kind_t::VALUE_TEXT},
567
    {"integer"_frag, value_kind_t::VALUE_INTEGER},
568
    {"float"_frag, value_kind_t::VALUE_FLOAT},
569
    {"boolean"_frag, value_kind_t::VALUE_BOOLEAN},
570
    {"json"_frag, value_kind_t::VALUE_JSON},
571
    {"struct"_frag, value_kind_t::VALUE_STRUCT},
572
    {"quoted"_frag, value_kind_t::VALUE_QUOTED},
573
    {"xml"_frag, value_kind_t::VALUE_XML},
574

575
    json_path_handler_base::ENUM_TERMINATOR,
576
};
577

578
static constexpr json_path_handler_base::enum_value_t SCALE_OP_ENUM[] = {
579
    {"identity"_frag, scale_op_t::SO_IDENTITY},
580
    {"multiply"_frag, scale_op_t::SO_MULTIPLY},
581
    {"divide"_frag, scale_op_t::SO_DIVIDE},
582

583
    json_path_handler_base::ENUM_TERMINATOR,
584
};
585

586
static const struct json_path_container scaling_factor_handlers = {
587
    yajlpp::property_handler("op")
588
        .with_enum_values(SCALE_OP_ENUM)
589
        .for_field(&scaling_factor::sf_op),
590

591
    yajlpp::property_handler("value").for_field(&scaling_factor::sf_value),
592
};
593

594
static const struct json_path_container scale_handlers = {
595
    yajlpp::pattern_property_handler("(?<scale>[^/]+)")
596
        .with_obj_provider(scaling_factor_provider)
597
        .with_children(scaling_factor_handlers),
598
};
599

600
static const struct json_path_container unit_handlers = {
601
    yajlpp::property_handler("field")
602
        .with_synopsis("<field-name>")
603
        .with_description(
604
            "The name of the field that contains the units for this field")
605
        .for_field(&external_log_format::value_def::vd_unit_field),
606

607
    yajlpp::property_handler("scaling-factor")
608
        .with_description("Transforms the numeric value by the given factor")
609
        .with_children(scale_handlers),
610
};
611

612
static const struct json_path_container value_def_handlers = {
613
    yajlpp::property_handler("kind")
614
        .with_synopsis("<data-type>")
615
        .with_description("The type of data in the field")
616
        .with_enum_values(KIND_ENUM)
617
        .for_field(&external_log_format::value_def::vd_meta,
618
                   &logline_value_meta::lvm_kind),
619

620
    yajlpp::property_handler("collate")
621
        .with_synopsis("<function>")
622
        .with_description("The collating function to use for this column")
623
        .for_field(&external_log_format::value_def::vd_collate),
624

625
    yajlpp::property_handler("unit")
626
        .with_description("Unit definitions for this field")
627
        .with_children(unit_handlers),
628

629
    yajlpp::property_handler("identifier")
630
        .with_synopsis("<bool>")
631
        .with_description("Indicates whether or not this field contains an "
632
                          "identifier that should be highlighted")
633
        .for_field(&external_log_format::value_def::vd_meta,
634
                   &logline_value_meta::lvm_identifier),
635

636
    yajlpp::property_handler("foreign-key")
637
        .with_synopsis("<bool>")
638
        .with_description("Indicates whether or not this field should be "
639
                          "treated as a foreign key for row in another table")
640
        .for_field(&external_log_format::value_def::vd_meta,
641
                   &logline_value_meta::lvm_foreign_key),
642

643
    yajlpp::property_handler("hidden")
644
        .with_synopsis("<bool>")
645
        .with_description(
646
            "Indicates whether or not this field should be hidden")
647
        .for_field(&external_log_format::value_def::vd_meta,
648
                   &logline_value_meta::lvm_hidden),
649

650
    yajlpp::property_handler("action-list#")
651
        .with_synopsis("<string>")
652
        .with_description("Actions to execute when this field is clicked on")
653
        .for_field(&external_log_format::value_def::vd_action_list),
654

655
    yajlpp::property_handler("rewriter")
656
        .with_synopsis("<command>")
657
        .with_description(
658
            "A command that will rewrite this field when pretty-printing")
659
        .for_field(&external_log_format::value_def::vd_rewriter)
660
        .with_example(
661
            ";SELECT :sc_status || ' (' || (SELECT message FROM "
662
            "http_status_codes WHERE status = :sc_status) || ') '"_frag),
663

664
    yajlpp::property_handler("description")
665
        .with_synopsis("<string>")
666
        .with_description("A description of the field")
667
        .for_field(&external_log_format::value_def::vd_description),
668
};
669

670
static const struct json_path_container highlighter_def_handlers = {
671
    yajlpp::property_handler("pattern")
672
        .with_synopsis("<regex>")
673
        .with_description(
674
            "A regular expression to highlight in logs of this format.")
675
        .for_field(&external_log_format::highlighter_def::hd_pattern),
676

677
    yajlpp::property_handler("color")
678
        .with_synopsis("#<hex>|<name>")
679
        .with_description("The color to use when highlighting this pattern.")
680
        .for_field(&external_log_format::highlighter_def::hd_color),
681

682
    yajlpp::property_handler("background-color")
683
        .with_synopsis("#<hex>|<name>")
684
        .with_description(
685
            "The background color to use when highlighting this pattern.")
686
        .for_field(&external_log_format::highlighter_def::hd_background_color),
687

688
    yajlpp::property_handler("underline")
689
        .with_synopsis("<enabled>")
690
        .with_description("Highlight this pattern with an underline.")
691
        .for_field(&external_log_format::highlighter_def::hd_underline),
692

693
    yajlpp::property_handler("blink")
694
        .with_synopsis("<enabled>")
695
        .with_description("Highlight this pattern by blinking.")
696
        .for_field(&external_log_format::highlighter_def::hd_blink),
697
    yajlpp::property_handler("nestable")
698
        .with_synopsis("<enabled>")
699
        .with_description("This highlight can be nested in another highlight.")
700
        .for_field(&external_log_format::highlighter_def::hd_nestable),
701
};
702

703
static const json_path_handler_base::enum_value_t LEVEL_ENUM[] = {
704
    {level_names[LEVEL_TRACE], LEVEL_TRACE},
705
    {level_names[LEVEL_DEBUG5], LEVEL_DEBUG5},
706
    {level_names[LEVEL_DEBUG4], LEVEL_DEBUG4},
707
    {level_names[LEVEL_DEBUG3], LEVEL_DEBUG3},
708
    {level_names[LEVEL_DEBUG2], LEVEL_DEBUG2},
709
    {level_names[LEVEL_DEBUG], LEVEL_DEBUG},
710
    {level_names[LEVEL_INFO], LEVEL_INFO},
711
    {level_names[LEVEL_STATS], LEVEL_STATS},
712
    {level_names[LEVEL_NOTICE], LEVEL_NOTICE},
713
    {level_names[LEVEL_WARNING], LEVEL_WARNING},
714
    {level_names[LEVEL_ERROR], LEVEL_ERROR},
715
    {level_names[LEVEL_CRITICAL], LEVEL_CRITICAL},
716
    {level_names[LEVEL_FATAL], LEVEL_FATAL},
717

718
    json_path_handler_base::ENUM_TERMINATOR,
719
};
720

721
static const struct json_path_container sample_handlers = {
722
    yajlpp::property_handler("description")
723
        .with_synopsis("<text>")
724
        .with_description("A description of this sample.")
725
        .for_field(&external_log_format::sample_t::s_description),
726
    yajlpp::property_handler("line")
727
        .with_synopsis("<log-line>")
728
        .with_description(
729
            "A sample log line that should match a pattern in this format.")
730
        .for_field(&external_log_format::sample_t::s_line),
731

732
    yajlpp::property_handler("level")
733
        .with_enum_values(LEVEL_ENUM)
734
        .with_description("The expected level for this sample log line.")
735
        .for_field(&external_log_format::sample_t::s_level),
736
};
737

738
static constexpr json_path_handler_base::enum_value_t TYPE_ENUM[] = {
739
    {"text"_frag, external_log_format::elf_type_t::ELF_TYPE_TEXT},
740
    {"json"_frag, external_log_format::elf_type_t::ELF_TYPE_JSON},
741
    {"csv"_frag, external_log_format::elf_type_t::ELF_TYPE_CSV},
742

743
    json_path_handler_base::ENUM_TERMINATOR,
744
};
745

746
static const struct json_path_container regex_handlers = {
747
    yajlpp::pattern_property_handler(R"((?<pattern_name>[^/]+))")
748
        .with_description("The set of patterns used to match log messages")
749
        .with_obj_provider(pattern_provider)
750
        .with_children(pattern_handlers),
751
};
752

753
static const struct json_path_container level_handlers = {
754
    yajlpp::pattern_property_handler("(?<level>trace|debug[2345]?|info|stats|"
755
                                     "notice|warning|error|critical|fatal)")
756
        .add_cb(read_levels)
757
        .add_cb(read_level_int)
758
        .with_synopsis("<pattern|integer>")
759
        .with_description("The regular expression used to match the log text "
760
                          "for this level.  "
761
                          "For JSON logs with numeric levels, this should be "
762
                          "the number for the corresponding level."),
763
};
764

765
static const struct json_path_container value_handlers = {
766
    yajlpp::pattern_property_handler("(?<value_name>[^/]+)")
767
        .with_description(
768
            "The set of values captured by the log message patterns")
769
        .with_obj_provider(value_def_provider)
770
        .with_children(value_def_handlers),
771
};
772

773
static const struct json_path_container tag_path_handlers = {
774
    yajlpp::property_handler("glob")
775
        .with_synopsis("<glob>")
776
        .with_description("The glob to match against file paths")
777
        .with_example("*/system.log*"_frag)
778
        .for_field(&format_tag_def::path_restriction::p_glob),
779
};
780

781
static const struct json_path_container format_tag_def_handlers = {
782
    yajlpp::property_handler("paths#")
783
        .with_description("Restrict tagging to the given paths")
784
        .for_field(&format_tag_def::ftd_paths)
785
        .with_children(tag_path_handlers),
786
    yajlpp::property_handler("pattern")
787
        .with_synopsis("<regex>")
788
        .with_description("The regular expression to match against the body of "
789
                          "the log message")
790
        .with_example("\\w+ is down"_frag)
791
        .for_field(&format_tag_def::ftd_pattern),
792
    yajlpp::property_handler("description")
793
        .with_synopsis("<string>")
794
        .with_description("A description of this tag")
795
        .for_field(&format_tag_def::ftd_description),
796
    json_path_handler("level")
797
        .with_synopsis("<log-level>")
798
        .with_description("Constrain hits to log messages with this level")
799
        .with_enum_values(LEVEL_ENUM)
800
        .for_field(&format_tag_def::ftd_level),
801
};
802

803
static const struct json_path_container tag_handlers = {
804
    yajlpp::pattern_property_handler(R"((?<tag_name>[\w:;\._\-]+))")
805
        .with_description("The name of the tag to apply")
806
        .with_obj_provider(format_tag_def_provider)
807
        .with_children(format_tag_def_handlers),
808
};
809

810
static const struct json_path_container format_partition_def_handlers = {
811
    yajlpp::property_handler("paths#")
812
        .with_description("Restrict partitioning to the given paths")
813
        .for_field(&format_partition_def::fpd_paths)
814
        .with_children(tag_path_handlers),
815
    yajlpp::property_handler("pattern")
816
        .with_synopsis("<regex>")
817
        .with_description("The regular expression to match against the body of "
818
                          "the log message")
819
        .with_example("\\w+ is down"_frag)
820
        .for_field(&format_partition_def::fpd_pattern),
821
    yajlpp::property_handler("description")
822
        .with_synopsis("<string>")
823
        .with_description("A description of this partition")
824
        .for_field(&format_partition_def::fpd_description),
825
    json_path_handler("level")
826
        .with_synopsis("<log-level>")
827
        .with_description("Constrain hits to log messages with this level")
828
        .with_enum_values(LEVEL_ENUM)
829
        .for_field(&format_partition_def::fpd_level),
830
};
831

832
static const struct json_path_container partition_handlers = {
833
    yajlpp::pattern_property_handler(R"((?<partition_type>[\w:;\._\-]+))")
834
        .with_description("The type of partition to apply")
835
        .with_obj_provider(format_partition_def_provider)
836
        .with_children(format_partition_def_handlers),
837
};
838

839
static const struct json_path_container highlight_handlers = {
840
    yajlpp::pattern_property_handler(R"((?<highlight_name>[^/]+))")
841
        .with_description("The definition of a highlight")
842
        .with_obj_provider<external_log_format::highlighter_def,
843
                           external_log_format>(
844
            [](const yajlpp_provider_context& ypc, external_log_format* root) {
1,639✔
845
                auto* retval
846
                    = &(root->elf_highlighter_patterns[ypc.get_substr_i(0)]);
1,639✔
847

848
                return retval;
1,639✔
849
            })
850
        .with_children(highlighter_def_handlers),
851
};
852

853
static const struct json_path_container action_def_handlers = {
854
    json_path_handler("label", read_action_def),
855
    json_path_handler("capture-output", read_action_bool),
856
    json_path_handler("cmd#", read_action_cmd),
857
};
858

859
static const struct json_path_container action_handlers = {
860
    json_path_handler(
861
        lnav::pcre2pp::code::from_const("(?<action_name>\\w+)").to_shared(),
862
        read_action_def)
863
        .with_children(action_def_handlers),
864
};
865

866
static const struct json_path_container search_table_def_handlers = {
867
    json_path_handler("pattern")
868
        .with_synopsis("<regex>")
869
        .with_description("The regular expression for this search table.")
870
        .for_field(&external_log_format::search_table_def::std_pattern),
871
    json_path_handler("glob")
872
        .with_synopsis("<glob>")
873
        .with_description("Glob pattern used to constrain hits to messages "
874
                          "that match the given pattern.")
875
        .for_field(&external_log_format::search_table_def::std_glob),
876
    json_path_handler("level")
877
        .with_synopsis("<log-level>")
878
        .with_description("Constrain hits to log messages with this level")
879
        .with_enum_values(LEVEL_ENUM)
880
        .for_field(&external_log_format::search_table_def::std_level),
881
};
882

883
static const struct json_path_container search_table_handlers = {
884
    yajlpp::pattern_property_handler("(?<table_name>\\w+)")
885
        .with_description(
886
            "The set of search tables to be automatically defined")
887
        .with_obj_provider<external_log_format::search_table_def,
888
                           external_log_format>(
889
            [](const yajlpp_provider_context& ypc, external_log_format* root) {
31,866✔
890
                auto* retval = &(root->elf_search_tables[ypc.get_substr_i(0)]);
31,866✔
891

892
                return retval;
31,866✔
893
            })
894
        .with_children(search_table_def_handlers),
895
};
896

897
static const struct json_path_container header_expr_handlers = {
898
    yajlpp::pattern_property_handler(R"((?<header_expr_name>\w+))")
899
        .with_description("SQLite expression")
900
        .for_field(&external_log_format::header_exprs::he_exprs),
901
};
902

903
static const struct json_path_container header_handlers = {
904
    yajlpp::property_handler("expr")
905
        .with_description("The expressions used to check if a file header "
906
                          "matches this file format")
907
        .for_child(&external_log_format::header::h_exprs)
908
        .with_children(header_expr_handlers),
909
    yajlpp::property_handler("size")
910
        .with_description("The minimum size required for this header type")
911
        .for_field(&external_log_format::header::h_size),
912
};
913

914
static const struct json_path_container converter_handlers = {
915
    yajlpp::property_handler("type")
916
        .with_description("The MIME type")
917
        .for_field(&external_log_format::converter::c_type),
918
    yajlpp::property_handler("header")
919
        .with_description("File header detection definitions")
920
        .for_child(&external_log_format::converter::c_header)
921
        .with_children(header_handlers),
922
    yajlpp::property_handler("command")
923
        .with_description("The script used to convert the file")
924
        .with_pattern(R"([\w\.\-]+)")
925
        .for_field(&external_log_format::converter::c_command),
926
};
927

928
static const struct json_path_container opid_descriptor_handlers = {
929
    yajlpp::property_handler("field")
930
        .with_synopsis("<name>")
931
        .with_description("The field to include in the operation description")
932
        .for_field(&log_format::opid_descriptor::od_field),
933
    yajlpp::property_handler("extractor")
934
        .with_synopsis("<regex>")
935
        .with_description(
936
            "The regex used to extract content for the operation description")
937
        .for_field(&log_format::opid_descriptor::od_extractor),
938
    yajlpp::property_handler("prefix")
939
        .with_description(
940
            "A string to prepend to this field in the description")
941
        .for_field(&log_format::opid_descriptor::od_prefix),
942
    yajlpp::property_handler("suffix")
943
        .with_description("A string to append to this field in the description")
944
        .for_field(&log_format::opid_descriptor::od_suffix),
945
    yajlpp::property_handler("joiner")
946
        .with_description("A string to insert between instances of this field "
947
                          "when the field is found more than once")
948
        .for_field(&log_format::opid_descriptor::od_joiner),
949
};
950

951
static const struct json_path_container opid_description_format_handlers = {
952
    yajlpp::property_handler("format#")
953
        .with_description("Defines the elements of this operation description")
954
        .for_field(&log_format::opid_descriptors::od_descriptors)
955
        .with_children(opid_descriptor_handlers),
956
};
957

958
static const struct json_path_container opid_description_handlers = {
959
    yajlpp::pattern_property_handler(R"((?<opid_descriptor>[\w\.\-]+))")
960
        .with_description("A type of description for this operation")
961
        .for_field(&log_format::lf_opid_description_def)
962
        .with_children(opid_description_format_handlers),
963
};
964

965
static const struct json_path_container subid_description_handlers = {
966
    yajlpp::pattern_property_handler(R"((?<subid_descriptor>[\w\.\-]+))")
967
        .with_description("A type of description for this sub-operation")
968
        .for_field(&log_format::lf_subid_description_def)
969
        .with_children(opid_description_format_handlers),
970
};
971

972
static const struct json_path_container opid_handlers = {
973
    yajlpp::property_handler("subid")
974
        .with_description("The field that holds the ID for a sub-operation")
975
        .for_field(&external_log_format::elf_subid_field),
976
    yajlpp::property_handler("description")
977
        .with_description(
978
            "Define how to construct a description of an operation")
979
        .with_children(opid_description_handlers),
980
    yajlpp::property_handler("sub-description")
981
        .with_description(
982
            "Define how to construct a description of a sub-operation")
983
        .with_children(subid_description_handlers),
984
};
985

986
const struct json_path_container format_handlers = {
987
    yajlpp::property_handler("regex")
988
        .with_description(
989
            "The set of regular expressions used to match log messages")
990
        .with_children(regex_handlers),
991

992
    json_path_handler("json", read_format_bool)
993
        .with_description(
994
            R"(Indicates that log files are JSON-encoded (deprecated, use "file-type": "json"))"),
995
    json_path_handler("convert-to-local-time")
996
        .with_description("Indicates that displayed timestamps should "
997
                          "automatically be converted to local time")
998
        .for_field(&external_log_format::lf_date_time,
999
                   &date_time_scanner::dts_local_time),
1000
    json_path_handler("hide-extra")
1001
        .with_description(
1002
            "Specifies whether extra values in JSON logs should be displayed")
1003
        .for_field(&external_log_format::jlf_hide_extra),
1004
    json_path_handler("multiline")
1005
        .with_description("Indicates that log messages can span multiple lines")
1006
        .for_field(&log_format::lf_multiline),
1007
    json_path_handler("timestamp-divisor", read_format_double)
1008
        .add_cb(read_format_int)
1009
        .with_synopsis("<number>")
1010
        .with_description(
1011
            "The value to divide a numeric timestamp by in a JSON log."),
1012
    json_path_handler("file-pattern")
1013
        .with_description("A regular expression that restricts this format to "
1014
                          "log files with a matching name")
1015
        .for_field(&external_log_format::elf_filename_pcre),
1016
    json_path_handler("converter")
1017
        .with_description("Describes how the file format can be detected and "
1018
                          "converted to a log that can be understood by lnav")
1019
        .for_child(&external_log_format::elf_converter)
1020
        .with_children(converter_handlers),
1021
    json_path_handler("level-field")
1022
        .with_description(
1023
            "The name of the level field in the log message pattern")
1024
        .for_field(&external_log_format::elf_level_field),
1025
    json_path_handler("level-pointer")
1026
        .with_description("A regular-expression that matches the JSON-pointer "
1027
                          "of the level property")
1028
        .for_field(&external_log_format::elf_level_pointer),
1029
    json_path_handler("timestamp-field")
1030
        .with_description(
1031
            "The name of the timestamp field in the log message pattern")
1032
        .for_field(&log_format::lf_timestamp_field),
1033
    json_path_handler("subsecond-field")
1034
        .with_description("The path to the property in a JSON-lines log "
1035
                          "message that contains the sub-second time value")
1036
        .for_field(&log_format::lf_subsecond_field),
1037
    json_path_handler("subsecond-units")
1038
        .with_description("The units of the subsecond-field property value")
1039
        .with_enum_values(SUBSECOND_UNIT_ENUM)
1040
        .for_field(&log_format::lf_subsecond_unit),
1041
    json_path_handler("time-field")
1042
        .with_description(
1043
            "The name of the time field in the log message pattern.  This "
1044
            "field should only be specified if the timestamp field only "
1045
            "contains a date.")
1046
        .for_field(&log_format::lf_time_field),
1047
    json_path_handler("timestamp-point-of-reference")
1048
        .with_description("The relation of the timestamp to the operation that "
1049
                          "the message refers to.")
1050
        .with_enum_values(TS_POR_ENUM)
1051
        .for_field(&external_log_format::lf_timestamp_point_of_reference),
1052
    json_path_handler("body-field")
1053
        .with_description(
1054
            "The name of the body field in the log message pattern")
1055
        .for_field(&external_log_format::elf_body_field),
1056
    json_path_handler("thread-id-field")
1057
        .with_description(
1058
            "The name of the thread ID field in the log message pattern")
1059
        .for_field(&external_log_format::elf_thread_id_field),
1060
    json_path_handler("src-file-field")
1061
        .with_description(
1062
            "The name of the source file field in the log message pattern")
1063
        .for_field(&external_log_format::elf_src_file_field),
1064
    json_path_handler("src-line-field")
1065
        .with_description(
1066
            "The name of the source line field in the log message pattern")
1067
        .for_field(&external_log_format::elf_src_line_field),
1068
    json_path_handler("duration-field")
1069
        .with_description(
1070
            "The name of the duration field in the log message pattern")
1071
        .for_field(&external_log_format::elf_duration_field),
1072
    json_path_handler("url",
1073
                      lnav::pcre2pp::code::from_const("^url#?").to_shared())
1074
        .add_cb(read_format_field)
1075
        .with_description("A URL with more information about this log format"),
1076
    json_path_handler("title", read_format_field)
1077
        .with_description("The human-readable name for this log format"),
1078
    json_path_handler("description")
1079
        .with_description("A longer description of this log format")
1080
        .for_field(&external_log_format::lf_description),
1081
    json_path_handler("timestamp-format#", read_format_field)
1082
        .with_description("An array of strptime(3)-like timestamp formats"),
1083
    json_path_handler("module-field", read_format_field)
1084
        .with_description(
1085
            "The name of the module field in the log message pattern"),
1086
    json_path_handler("opid-field")
1087
        .with_description(
1088
            "The name of the operation-id field in the log message pattern")
1089
        .for_field(&external_log_format::elf_opid_field),
1090
    yajlpp::property_handler("opid")
1091
        .with_description("Definitions related to operations found in logs")
1092
        .with_children(opid_handlers),
1093
    yajlpp::property_handler("ordered-by-time")
1094
        .with_synopsis("<bool>")
1095
        .with_description(
1096
            "Indicates that the order of messages in the file is time-based.")
1097
        .for_field(&log_format::lf_time_ordered),
1098
    yajlpp::property_handler("level")
1099
        .with_description(
1100
            "The map of level names to patterns or integer values")
1101
        .with_children(level_handlers),
1102

1103
    yajlpp::property_handler("value")
1104
        .with_description("The set of value definitions")
1105
        .with_children(value_handlers),
1106

1107
    yajlpp::property_handler("tags")
1108
        .with_description("The tags to automatically apply to log messages")
1109
        .with_children(tag_handlers),
1110

1111
    yajlpp::property_handler("partitions")
1112
        .with_description(
1113
            "The partitions to automatically apply to log messages")
1114
        .with_children(partition_handlers),
1115

1116
    yajlpp::property_handler("action").with_children(action_handlers),
1117
    yajlpp::property_handler("sample#")
1118
        .with_description("An array of sample log messages to be tested "
1119
                          "against the log message patterns")
1120
        .with_obj_provider(sample_provider)
1121
        .with_children(sample_handlers),
1122

1123
    yajlpp::property_handler("line-format#")
1124
        .with_description("The display format for JSON-encoded log messages")
1125
        .with_obj_provider(line_format_provider)
1126
        .add_cb(read_json_constant)
1127
        .with_children(line_format_handlers),
1128
    json_path_handler("search-table")
1129
        .with_description(
1130
            "Search tables to automatically define for this log format")
1131
        .with_children(search_table_handlers),
1132

1133
    yajlpp::property_handler("highlights")
1134
        .with_description("The set of highlight definitions")
1135
        .with_children(highlight_handlers),
1136

1137
    yajlpp::property_handler("file-type")
1138
        .with_synopsis("text|json|csv")
1139
        .with_description("The type of file that contains the log messages")
1140
        .with_enum_values(TYPE_ENUM)
1141
        .for_field(&external_log_format::elf_type),
1142

1143
    yajlpp::property_handler("max-unrecognized-lines")
1144
        .with_synopsis("<lines>")
1145
        .with_description("The maximum number of lines in a file to use when "
1146
                          "detecting the format")
1147
        .with_min_value(1)
1148
        .for_field(&log_format::lf_max_unrecognized_lines),
1149
};
1150

1151
static int
1152
read_id(yajlpp_parse_context* ypc,
50,716✔
1153
        const unsigned char* str,
1154
        size_t len,
1155
        yajl_string_props_t*)
1156
{
1157
    auto* ud = static_cast<loader_userdata*>(ypc->ypc_userdata);
50,716✔
1158
    auto file_id = std::string((const char*) str, len);
50,716✔
1159

1160
    ud->ud_file_schema = file_id;
50,716✔
1161
    if (SUPPORTED_FORMAT_SCHEMAS.find(file_id)
50,716✔
1162
        == SUPPORTED_FORMAT_SCHEMAS.end())
101,432✔
1163
    {
1164
        const auto* handler = ypc->ypc_current_handler;
1✔
1165
        attr_line_t notes{"expecting one of the following $schema values:"};
1✔
1166

1167
        for (const auto& schema : SUPPORTED_FORMAT_SCHEMAS) {
2✔
1168
            notes.append("\n").append(
2✔
1169
                lnav::roles::symbol(fmt::format(FMT_STRING("  {}"), schema)));
5✔
1170
        }
1171
        ypc->report_error(
2✔
1172
            lnav::console::user_message::error(
×
1173
                attr_line_t("'")
1✔
1174
                    .append(lnav::roles::symbol(file_id))
2✔
1175
                    .append("' is not a supported log format $schema version"))
1✔
1176
                .with_snippet(ypc->get_snippet())
2✔
1177
                .with_note(notes)
1✔
1178
                .with_help(handler->get_help_text(ypc)));
1✔
1179
    }
1✔
1180

1181
    return 1;
50,716✔
1182
}
50,716✔
1183

1184
const struct json_path_container root_format_handler = json_path_container{
1185
    json_path_handler("$schema", read_id)
1186
        .with_synopsis("The URI of the schema for this file")
1187
        .with_description("Specifies the type of this file"),
1188

1189
    yajlpp::pattern_property_handler("(?<format_name>\\w+)")
1190
        .with_description("The definition of a log file format.")
1191
        .with_obj_provider(ensure_format)
1192
        .with_children(format_handlers),
1193
}
1194
    .with_schema_id(DEFAULT_FORMAT_SCHEMA);
1195

1196
static void
1197
write_sample_file()
741✔
1198
{
1199
    const auto dstdir = lnav::paths::dotlnav();
741✔
1200
    for (const auto& bsf : lnav_format_json) {
50,388✔
1201
        auto sample_path = dstdir
1202
            / fmt::format(FMT_STRING("formats/default/{}.sample"),
198,588✔
1203
                          bsf.get_name());
99,294✔
1204

1205
        const auto& name_sf = bsf.get_name();
49,647✔
1206
        auto stat_res = lnav::filesystem::stat_file(sample_path);
49,647✔
1207
        if (stat_res.isOk()) {
49,647✔
1208
            auto st = stat_res.unwrap();
42,478✔
1209
            if (st.st_mtime >= lnav::filesystem::self_mtime()) {
42,478✔
1210
                log_debug("skipping writing sample: %.*s (mtimes %ld >= %lld)",
42,478✔
1211
                          name_sf.length(),
1212
                          name_sf.data(),
1213
                          st.st_mtime,
1214
                          lnav::filesystem::self_mtime());
1215
                continue;
42,478✔
1216
            }
NEW
1217
            log_debug("sample file needs to be updated: %.*s",
×
1218
                      name_sf.length(),
1219
                      name_sf.data());
1220
        } else {
1221
            log_debug("sample file does not exist: %.*s",
7,169✔
1222
                      name_sf.length(),
1223
                      name_sf.data());
1224
        }
1225

1226
        auto sfp = bsf.to_string_fragment_producer();
7,169✔
1227
        auto write_res = lnav::filesystem::write_file(
1228
            sample_path,
1229
            *sfp,
7,169✔
1230
            {lnav::filesystem::write_file_options::read_only});
14,338✔
1231

1232
        if (write_res.isErr()) {
7,169✔
1233
            auto msg = write_res.unwrapErr();
4,556✔
1234
            fprintf(stderr,
4,556✔
1235
                    "error:unable to write default format file: %s -- %s\n",
1236
                    sample_path.c_str(),
1237
                    msg.c_str());
1238
        }
4,556✔
1239
    }
92,125✔
1240

1241
    for (const auto& bsf : lnav_sh_scripts) {
3,705✔
1242
        auto sh_path = dstdir
1243
            / fmt::format(FMT_STRING("formats/default/{}"), bsf.get_name());
11,856✔
1244
        auto stat_res = lnav::filesystem::stat_file(sh_path);
2,964✔
1245
        if (stat_res.isOk()) {
2,964✔
1246
            auto st = stat_res.unwrap();
2,536✔
1247
            if (st.st_mtime >= lnav::filesystem::self_mtime()) {
2,536✔
1248
                continue;
2,536✔
1249
            }
1250
        }
1251

1252
        auto sfp = bsf.to_string_fragment_producer();
428✔
1253
        auto write_res = lnav::filesystem::write_file(
1254
            sh_path,
1255
            *sfp,
428✔
1256
            {
1257
                lnav::filesystem::write_file_options::executable,
1258
                lnav::filesystem::write_file_options::read_only,
1259
            });
856✔
1260

1261
        if (write_res.isErr()) {
428✔
1262
            auto msg = write_res.unwrapErr();
272✔
1263
            fprintf(stderr,
272✔
1264
                    "error:unable to write default text file: %s -- %s\n",
1265
                    sh_path.c_str(),
1266
                    msg.c_str());
1267
        }
272✔
1268
    }
5,500✔
1269

1270
    for (const auto& bsf : lnav_scripts) {
14,079✔
1271
        script_metadata meta;
13,338✔
1272
        auto sf = bsf.to_string_fragment_producer()->to_string();
13,338✔
1273

1274
        meta.sm_name = bsf.get_name().to_string();
13,338✔
1275
        extract_metadata(sf, meta);
13,338✔
1276
        auto path
1277
            = fmt::format(FMT_STRING("formats/default/{}.lnav"), meta.sm_name);
40,014✔
1278
        auto script_path = dstdir / path;
13,338✔
1279
        auto stat_res = lnav::filesystem::stat_file(script_path);
13,338✔
1280
        if (stat_res.isOk()) {
13,338✔
1281
            auto st = stat_res.unwrap();
11,451✔
1282
            if (st.st_mtime >= lnav::filesystem::self_mtime()) {
11,451✔
1283
                continue;
11,451✔
1284
            }
1285
        }
1286

1287
        auto write_res = lnav::filesystem::write_file(
1288
            script_path,
1289
            sf,
1290
            {
1291
                lnav::filesystem::write_file_options::executable,
1292
                lnav::filesystem::write_file_options::read_only,
1293
            });
1,887✔
1294
        if (write_res.isErr()) {
1,887✔
1295
            fprintf(stderr,
1,224✔
1296
                    "error:unable to write default script file: %s -- %s\n",
1297
                    script_path.c_str(),
1298
                    strerror(errno));
1,224✔
1299
        }
1300
    }
59,142✔
1301
}
741✔
1302

1303
static void
1304
format_error_reporter(const yajlpp_parse_context& ypc,
16✔
1305
                      const lnav::console::user_message& msg)
1306
{
1307
    auto* ud = (loader_userdata*) ypc.ypc_userdata;
16✔
1308

1309
    ud->ud_errors->emplace_back(msg);
16✔
1310
}
16✔
1311

1312
std::vector<intern_string_t>
1313
load_format_file(const std::filesystem::path& filename,
1,071✔
1314
                 std::vector<lnav::console::user_message>& errors)
1315
{
1316
    std::vector<intern_string_t> retval;
1,071✔
1317
    loader_userdata ud;
1,071✔
1318
    auto_fd fd;
1,071✔
1319

1320
    log_info("loading formats from file: %s", filename.c_str());
1,071✔
1321
    yajlpp_parse_context ypc(intern_string::lookup(filename.string()),
2,142✔
1322
                             &root_format_handler);
1,071✔
1323
    ud.ud_parse_context = &ypc;
1,071✔
1324
    ud.ud_format_path = filename;
1,071✔
1325
    ud.ud_format_names = &retval;
1,071✔
1326
    ud.ud_errors = &errors;
1,071✔
1327
    ypc.ypc_userdata = &ud;
1,071✔
1328
    ypc.with_obj(ud);
1,071✔
1329
    if ((fd = lnav::filesystem::openp(filename, O_RDONLY)) == -1) {
1,071✔
1330
        errors.emplace_back(
×
1331
            lnav::console::user_message::error(
×
1332
                attr_line_t("unable to open format file: ")
×
1333
                    .append(lnav::roles::file(filename.string())))
×
1334
                .with_errno_reason());
1335
    } else {
1336
        auto_mem<yajl_handle_t> handle(yajl_free);
1,071✔
1337
        char buffer[2048];
1338
        off_t offset = 0;
1,071✔
1339
        ssize_t rc = -1;
1,071✔
1340

1341
        handle = yajl_alloc(&ypc.ypc_callbacks, nullptr, &ypc);
1,071✔
1342
        ypc.with_handle(handle).with_error_reporter(format_error_reporter);
1,071✔
1343
        yajl_config(handle, yajl_allow_comments, 1);
1,071✔
1344
        while (true) {
1345
            rc = read(fd, buffer, sizeof(buffer));
2,333✔
1346
            if (rc == 0) {
2,333✔
1347
                break;
1,070✔
1348
            }
1349
            if (rc == -1) {
1,263✔
1350
                errors.emplace_back(
×
1351
                    lnav::console::user_message::error(
×
1352
                        attr_line_t("unable to read format file: ")
×
1353
                            .append(lnav::roles::file(filename.string())))
×
1354
                        .with_errno_reason());
1355
                break;
×
1356
            }
1357
            if (offset == 0 && (rc > 2) && (buffer[0] == '#')
1,263✔
1358
                && (buffer[1] == '!'))
×
1359
            {
1360
                // Turn it into a JavaScript comment.
1361
                buffer[0] = buffer[1] = '/';
×
1362
            }
1363
            if (ypc.parse((const unsigned char*) buffer, rc) != yajl_status_ok)
1,263✔
1364
            {
1365
                break;
1✔
1366
            }
1367
            offset += rc;
1,262✔
1368
        }
1369
        if (rc == 0) {
1,071✔
1370
            ypc.complete_parse();
1,070✔
1371
        }
1372

1373
        if (ud.ud_file_schema.empty()) {
1,071✔
1374
            static const auto SCHEMA_LINE
1375
                = attr_line_t()
2✔
1376
                      .append(
4✔
1377
                          fmt::format(FMT_STRING("    \"$schema\": \"{}\","),
8✔
1378
                                      *SUPPORTED_FORMAT_SCHEMAS.begin()))
4✔
1379
                      .with_attr_for_all(VC_ROLE.value(role_t::VCR_QUOTED_CODE))
4✔
1380
                      .move();
4✔
1381

1382
            errors.emplace_back(
2✔
1383
                lnav::console::user_message::warning(
×
1384
                    attr_line_t("format file is missing ")
4✔
1385
                        .append_quoted("$schema"_symbol)
2✔
1386
                        .append(" property"))
2✔
1387
                    .with_snippet(lnav::console::snippet::from(
6✔
1388
                        intern_string::lookup(filename.string()), ""))
4✔
1389
                    .with_note("the schema specifies the supported format "
4✔
1390
                               "version and can be used with editors to "
1391
                               "automatically validate the file")
1392
                    .with_help(attr_line_t("add the following property to the "
4✔
1393
                                           "top-level JSON object:\n")
1394
                                   .append(SCHEMA_LINE)));
2✔
1395
        }
1396
    }
1,071✔
1397

1398
    return retval;
2,142✔
1399
}
1,071✔
1400

1401
static void
1402
load_from_path(const std::filesystem::path& path,
1,979✔
1403
               std::vector<lnav::console::user_message>& errors)
1404
{
1405
    auto format_path = path / "formats/*/*.json";
1,979✔
1406
    static_root_mem<glob_t, globfree> gl;
1,979✔
1407

1408
    log_info("loading formats from path: %s", format_path.c_str());
1,979✔
1409
    if (glob(format_path.c_str(), 0, nullptr, gl.inout()) == 0) {
1,979✔
1410
        for (int lpc = 0; lpc < (int) gl->gl_pathc; lpc++) {
1,166✔
1411
            auto filepath = std::filesystem::path(gl->gl_pathv[lpc]);
1,068✔
1412

1413
            if (startswith(filepath.filename().string(), "config.")) {
1,068✔
1414
                log_info("  not loading config as format: %s",
×
1415
                         filepath.c_str());
1416
                continue;
×
1417
            }
1418

1419
            auto format_list = load_format_file(filepath, errors);
1,068✔
1420
            if (format_list.empty()) {
1,068✔
1421
                log_warning("Empty format file: %s", filepath.c_str());
2✔
1422
            } else {
1423
                log_info("contents of format file '%s':", filepath.c_str());
1,066✔
1424
                for (auto iter = format_list.begin(); iter != format_list.end();
2,228✔
1425
                     ++iter)
1,162✔
1426
                {
1427
                    log_info("  found format: %s", iter->get());
1,162✔
1428
                }
1429
            }
1430
        }
1,068✔
1431
    }
1432
}
1,979✔
1433

1434
void
1435
load_formats(const std::vector<std::filesystem::path>& extra_paths,
741✔
1436
             std::vector<lnav::console::user_message>& errors)
1437
{
1438
    auto op_guard = lnav_opid_guard::once(__FUNCTION__);
741✔
1439

1440
    auto default_source = lnav::paths::dotlnav() / "default";
741✔
1441
    std::vector<intern_string_t> retval;
741✔
1442
    loader_userdata ud;
741✔
1443
    yajl_handle handle;
1444

1445
    write_sample_file();
741✔
1446

1447
    log_debug("Loading default formats");
741✔
1448
    for (const auto& bsf : lnav_format_json) {
50,388✔
1449
        yajlpp_parse_context ypc_builtin(intern_string::lookup(bsf.get_name()),
49,647✔
1450
                                         &root_format_handler);
49,647✔
1451
        handle = yajl_alloc(&ypc_builtin.ypc_callbacks, nullptr, &ypc_builtin);
49,647✔
1452
        ud.ud_parse_context = &ypc_builtin;
49,647✔
1453
        ud.ud_format_names = &retval;
49,647✔
1454
        ud.ud_errors = &errors;
49,647✔
1455
        ypc_builtin.with_obj(ud)
99,294✔
1456
            .with_handle(handle)
49,647✔
1457
            .with_error_reporter(format_error_reporter)
49,647✔
1458
            .ypc_userdata
1459
            = &ud;
99,294✔
1460
        yajl_config(handle, yajl_allow_comments, 1);
49,647✔
1461
        auto sf = bsf.to_string_fragment_producer();
49,647✔
1462
        ypc_builtin.parse(*sf);
49,647✔
1463
        yajl_free(handle);
49,647✔
1464
    }
49,647✔
1465

1466
    for (const auto& extra_path : extra_paths) {
2,720✔
1467
        load_from_path(extra_path, errors);
1,979✔
1468
    }
1469

1470
    uint8_t mod_counter = 0;
741✔
1471

1472
    std::vector<std::shared_ptr<external_log_format>> alpha_ordered_formats;
741✔
1473
    for (auto iter = LOG_FORMATS.begin(); iter != LOG_FORMATS.end(); ++iter) {
51,454✔
1474
        auto& elf = iter->second;
50,713✔
1475
        elf->build(errors);
50,713✔
1476

1477
        if (elf->elf_has_module_format) {
50,713✔
1478
            mod_counter += 1;
2,223✔
1479
            elf->lf_mod_index = mod_counter;
2,223✔
1480
        }
1481

1482
        for (auto& check_iter : LOG_FORMATS) {
3,531,590✔
1483
            if (iter->first == check_iter.first) {
3,480,877✔
1484
                continue;
50,713✔
1485
            }
1486

1487
            auto& check_elf = check_iter.second;
3,430,164✔
1488
            if (elf->match_samples(check_elf->elf_samples)) {
3,430,164✔
1489
                log_warning(
16,184✔
1490
                    "Format collision, format '%s' matches sample from '%s'",
1491
                    elf->get_name().get(),
1492
                    check_elf->get_name().get());
1493
                elf->elf_collision.push_back(check_elf->get_name());
16,184✔
1494
            }
1495
        }
1496

1497
        alpha_ordered_formats.push_back(elf);
50,713✔
1498
    }
1499

1500
    auto& graph_ordered_formats = external_log_format::GRAPH_ORDERED_FORMATS;
741✔
1501

1502
    while (!alpha_ordered_formats.empty()) {
9,475✔
1503
        std::vector<intern_string_t> popped_formats;
8,734✔
1504

1505
        for (auto iter = alpha_ordered_formats.begin();
8,734✔
1506
             iter != alpha_ordered_formats.end();)
97,017✔
1507
        {
1508
            auto elf = *iter;
88,283✔
1509
            if (elf->elf_collision.empty()) {
88,283✔
1510
                iter = alpha_ordered_formats.erase(iter);
50,713✔
1511
                popped_formats.push_back(elf->get_name());
50,713✔
1512
                graph_ordered_formats.push_back(elf);
50,713✔
1513
            } else {
1514
                ++iter;
37,570✔
1515
            }
1516
        }
88,283✔
1517

1518
        if (popped_formats.empty() && !alpha_ordered_formats.empty()) {
8,734✔
1519
            bool broke_cycle = false;
2,514✔
1520

1521
            log_warning("Detected a cycle...");
2,514✔
1522
            for (const auto& elf : alpha_ordered_formats) {
14,689✔
1523
                if (elf->elf_builtin_format) {
12,466✔
1524
                    log_warning("  Skipping builtin format -- %s",
12,175✔
1525
                                elf->get_name().get());
1526
                } else {
1527
                    log_warning("  Breaking cycle by picking -- %s",
291✔
1528
                                elf->get_name().get());
1529
                    elf->elf_collision.clear();
291✔
1530
                    broke_cycle = true;
291✔
1531
                    break;
291✔
1532
                }
1533
            }
1534
            if (!broke_cycle) {
2,514✔
1535
                alpha_ordered_formats.front()->elf_collision.clear();
2,223✔
1536
            }
1537
        }
1538

1539
        for (const auto& elf : alpha_ordered_formats) {
46,304✔
1540
            for (auto& popped_format : popped_formats) {
435,760✔
1541
                elf->elf_collision.remove(popped_format);
398,190✔
1542
            }
1543
        }
1544
    }
8,734✔
1545

1546
    log_info("Format order:") for (auto& graph_ordered_format :
1,482✔
1547
                                   graph_ordered_formats)
52,195✔
1548
    {
1549
        log_info("  %s", graph_ordered_format->get_name().get());
50,713✔
1550
    }
1551

1552
    auto& roots = log_format::get_root_formats();
741✔
1553
    auto iter = std::find_if(roots.begin(), roots.end(), [](const auto& elem) {
741✔
1554
        return elem->get_name() == "generic_log";
3,705✔
1555
    });
1556
    roots.insert(
741✔
1557
        iter, graph_ordered_formats.begin(), graph_ordered_formats.end());
1558
}
741✔
1559

1560
static void
1561
exec_sql_in_path(sqlite3* db,
1,933✔
1562
                 const std::map<std::string, scoped_value_t>& global_vars,
1563
                 const std::filesystem::path& path,
1564
                 std::vector<lnav::console::user_message>& errors)
1565
{
1566
    auto format_path = path / "formats/*/*.sql";
1,933✔
1567
    static_root_mem<glob_t, globfree> gl;
1,933✔
1568

1569
    log_info("executing SQL files in path: %s", format_path.c_str());
1,933✔
1570
    if (glob(format_path.c_str(), 0, nullptr, gl.inout()) == 0) {
1,933✔
1571
        for (int lpc = 0; lpc < (int) gl->gl_pathc; lpc++) {
1,357✔
1572
            auto filename = std::filesystem::path(gl->gl_pathv[lpc]);
679✔
1573
            auto read_res = lnav::filesystem::read_file(filename);
679✔
1574

1575
            if (read_res.isOk()) {
679✔
1576
                log_info("Executing SQL file: %s", filename.c_str());
679✔
1577
                auto content = read_res.unwrap();
679✔
1578

1579
                sql_execute_script(
679✔
1580
                    db, global_vars, filename.c_str(), content.c_str(), errors);
1581
            } else {
679✔
1582
                errors.emplace_back(
×
1583
                    lnav::console::user_message::error(
×
1584
                        attr_line_t("unable to read format file: ")
×
1585
                            .append(lnav::roles::file(filename.string())))
×
1586
                        .with_reason(read_res.unwrapErr()));
×
1587
            }
1588
        }
679✔
1589
    }
1590
}
1,933✔
1591

1592
void
1593
load_format_extra(sqlite3* db,
627✔
1594
                  const std::map<std::string, scoped_value_t>& global_vars,
1595
                  const std::vector<std::filesystem::path>& extra_paths,
1596
                  std::vector<lnav::console::user_message>& errors)
1597
{
1598
    for (const auto& extra_path : extra_paths) {
2,560✔
1599
        exec_sql_in_path(db, global_vars, extra_path, errors);
1,933✔
1600
    }
1601
}
627✔
1602

1603
static void
1604
extract_metadata(string_fragment contents, script_metadata& meta_out)
14,082✔
1605
{
1606
    static const auto SYNO_RE = lnav::pcre2pp::code::from_const(
1607
        "^#\\s+@synopsis:(.*)$", PCRE2_MULTILINE);
14,082✔
1608
    static const auto DESC_RE = lnav::pcre2pp::code::from_const(
1609
        "^#\\s+@description:(.*)$", PCRE2_MULTILINE);
14,082✔
1610
    static const auto OUTPUT_FORMAT_RE = lnav::pcre2pp::code::from_const(
1611
        "^#\\s+@output-format:\\s+(.*)$", PCRE2_MULTILINE);
14,082✔
1612

1613
    auto syno_md = SYNO_RE.create_match_data();
14,082✔
1614
    auto syno_match_res
1615
        = SYNO_RE.capture_from(contents).into(syno_md).matches().ignore_error();
14,082✔
1616
    if (syno_match_res) {
14,082✔
1617
        meta_out.sm_synopsis = syno_md[1]->trim().to_string();
14,043✔
1618
    }
1619
    auto desc_md = DESC_RE.create_match_data();
14,082✔
1620
    auto desc_match_res
1621
        = DESC_RE.capture_from(contents).into(desc_md).matches().ignore_error();
14,082✔
1622
    if (desc_match_res) {
14,082✔
1623
        meta_out.sm_description = desc_md[1]->trim().to_string();
14,043✔
1624
    }
1625

1626
    auto out_format_md = OUTPUT_FORMAT_RE.create_match_data();
14,082✔
1627
    auto out_format_res = OUTPUT_FORMAT_RE.capture_from(contents)
14,082✔
1628
                              .into(out_format_md)
14,082✔
1629
                              .matches()
28,164✔
1630
                              .ignore_error();
14,082✔
1631
    if (out_format_res) {
14,082✔
1632
        auto out_format_frag = out_format_md[1]->trim();
782✔
1633
        auto from_res = from<text_format_t>(out_format_frag);
782✔
1634
        if (from_res.isErr()) {
782✔
1635
            log_error("%s (%s): invalid @output-format '%.*s'",
×
1636
                      meta_out.sm_name.c_str(),
1637
                      meta_out.sm_path.c_str(),
1638
                      out_format_frag.length(),
1639
                      out_format_frag.data());
1640
        } else {
1641
            meta_out.sm_output_format = from_res.unwrap();
782✔
1642
            log_info("%s (%s): setting output format to %d",
782✔
1643
                     meta_out.sm_name.c_str(),
1644
                     meta_out.sm_path.c_str(),
1645
                     meta_out.sm_output_format);
1646
        }
1647
    }
782✔
1648

1649
    if (!meta_out.sm_synopsis.empty()) {
14,082✔
1650
        size_t space = meta_out.sm_synopsis.find(' ');
14,043✔
1651

1652
        if (space == std::string::npos) {
14,043✔
1653
            space = meta_out.sm_synopsis.size();
10,174✔
1654
        }
1655
        meta_out.sm_name = meta_out.sm_synopsis.substr(0, space);
14,043✔
1656
    }
1657
}
14,082✔
1658

1659
void
1660
extract_metadata_from_file(struct script_metadata& meta_inout)
744✔
1661
{
1662
    auto stat_res = lnav::filesystem::stat_file(meta_inout.sm_path);
744✔
1663
    if (stat_res.isErr()) {
744✔
1664
        log_warning("unable to open script: %s -- %s",
×
1665
                    meta_inout.sm_path.c_str(),
1666
                    stat_res.unwrapErr().c_str());
1667
        return;
×
1668
    }
1669

1670
    auto st = stat_res.unwrap();
744✔
1671
    if (!S_ISREG(st.st_mode)) {
744✔
1672
        log_warning("script is not a regular file -- %s",
×
1673
                    meta_inout.sm_path.c_str());
1674
        return;
×
1675
    }
1676

1677
    auto open_res = lnav::filesystem::open_file(meta_inout.sm_path, O_RDONLY);
744✔
1678
    if (open_res.isErr()) {
744✔
1679
        log_warning("unable to open script file: %s -- %s",
×
1680
                    meta_inout.sm_path.c_str(),
1681
                    open_res.unwrapErr().c_str());
1682
        return;
×
1683
    }
1684

1685
    auto fd = open_res.unwrap();
744✔
1686
    char buffer[8 * 1024];
1687
    auto rc = read(fd, buffer, sizeof(buffer));
744✔
1688
    if (rc > 0) {
744✔
1689
        extract_metadata(string_fragment::from_bytes(buffer, rc), meta_inout);
744✔
1690
    }
1691
}
744✔
1692

1693
static void
1694
find_format_in_path(const std::filesystem::path& path,
131✔
1695
                    available_scripts& scripts)
1696
{
1697
    for (const auto& format_path :
393✔
1698
         {path / "formats/*/*.lnav", path / "configs/*/*.lnav"})
786✔
1699
    {
1700
        static_root_mem<glob_t, globfree> gl;
262✔
1701

1702
        log_debug("Searching for script in path: %s", format_path.c_str());
262✔
1703
        if (glob(format_path.c_str(), 0, nullptr, gl.inout()) == 0) {
262✔
1704
            for (size_t lpc = 0; lpc < gl->gl_pathc; lpc++) {
794✔
1705
                const char* filename = basename(gl->gl_pathv[lpc]);
737✔
1706
                auto script_name = std::string(filename, strlen(filename) - 5);
737✔
1707
                struct script_metadata meta;
737✔
1708

1709
                meta.sm_path = gl->gl_pathv[lpc];
737✔
1710
                meta.sm_name = script_name;
737✔
1711
                extract_metadata_from_file(meta);
737✔
1712
                scripts.as_scripts[script_name].push_back(meta);
737✔
1713

1714
                log_info("  found script: %s", meta.sm_path.c_str());
737✔
1715
            }
737✔
1716
        }
1717
    }
655✔
1718
}
131✔
1719

1720
available_scripts
1721
find_format_scripts(const std::vector<std::filesystem::path>& extra_paths)
41✔
1722
{
1723
    available_scripts retval;
41✔
1724
    for (const auto& extra_path : extra_paths) {
172✔
1725
        find_format_in_path(extra_path, retval);
131✔
1726
    }
1727
    return retval;
41✔
1728
}
×
1729

1730
void
1731
load_format_vtabs(log_vtab_manager* vtab_manager,
627✔
1732
                  std::vector<lnav::console::user_message>& errors)
1733
{
1734
    auto& root_formats = LOG_FORMATS;
627✔
1735

1736
    for (auto& root_format : root_formats) {
43,196✔
1737
        root_format.second->register_vtabs(vtab_manager, errors);
42,569✔
1738
    }
1739
}
627✔
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