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

tstack / lnav / 18577582398-2579

16 Oct 2025 11:24PM UTC coverage: 69.212% (-0.9%) from 70.063%
18577582398-2579

push

github

tstack
[build] fix type

1 of 1 new or added line in 1 file covered. (100.0%)

2573 existing lines in 65 files now uncovered.

50113 of 72405 relevant lines covered (69.21%)

415651.95 hits per line

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

92.36
/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_loader.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_ext.hh"
56
#include "sql_execute.hh"
57
#include "sql_util.hh"
58
#include "yajlpp/yajlpp.hh"
59
#include "yajlpp/yajlpp_def.hh"
60

61
static void extract_metadata(string_fragment, struct script_metadata& meta_out);
62

63
using log_formats_map_t
64
    = std::map<intern_string_t, std::shared_ptr<external_log_format>>;
65

66
using namespace lnav::roles::literals;
67

68
static auto intern_lifetime = intern_string::get_table_lifetime();
69
static log_formats_map_t LOG_FORMATS;
70

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

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

92
    if (find(formats->begin(), formats->end(), name) == formats->end()) {
3,466,762✔
93
        formats->push_back(name);
48,331✔
94
    }
95

96
    if (!ud->ud_format_path.empty()) {
3,466,762✔
97
        const intern_string_t i_src_path
98
            = intern_string::lookup(ud->ud_format_path.string());
39,767✔
99
        auto srcs_iter = retval->elf_format_sources.find(i_src_path);
39,767✔
100
        if (srcs_iter == retval->elf_format_sources.end()) {
39,767✔
101
            retval->elf_format_source_order.emplace_back(ud->ud_format_path);
1,141✔
102
            retval->elf_format_sources[i_src_path]
2,282✔
103
                = ud->ud_parse_context->get_line_number();
1,141✔
104
        }
105
    }
106

107
    if (ud->ud_format_path.empty()) {
3,466,762✔
108
        retval->elf_builtin_format = true;
3,426,995✔
109
    }
110

111
    return retval;
3,466,762✔
112
}
113

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

120
    if (pat.get() == nullptr) {
280,578✔
121
        pat = std::make_shared<external_log_format::pattern>();
92,811✔
122
    }
123

124
    if (pat->p_config_path.empty()) {
280,578✔
125
        pat->p_name = intern_string::lookup(regex_name);
92,811✔
126
        pat->p_config_path = fmt::format(
185,622✔
127
            FMT_STRING("/{}/regex/{}"), elf->get_name(), regex_name);
464,055✔
128
    }
129

130
    return pat.get();
561,156✔
131
}
280,578✔
132

133
static external_log_format::value_def*
134
value_def_provider(const yajlpp_provider_context& ypc, external_log_format* elf)
1,571,270✔
135
{
136
    const intern_string_t value_name = ypc.get_substr_i(0);
1,571,270✔
137

138
    auto iter = elf->elf_value_defs.find(value_name);
1,571,270✔
139
    std::shared_ptr<external_log_format::value_def> retval;
1,571,270✔
140

141
    if (iter == elf->elf_value_defs.end()) {
1,571,270✔
142
        retval = std::make_shared<external_log_format::value_def>(
403,150✔
143
            value_name,
144
            value_kind_t::VALUE_TEXT,
403,150✔
145
            logline_value_meta::external_column{},
×
146
            elf);
403,150✔
147
        elf->elf_value_defs[value_name] = retval;
403,150✔
148
        elf->elf_value_def_order.emplace_back(retval);
403,150✔
149
    } else {
150
        retval = iter->second;
1,168,120✔
151
    }
152

153
    return retval.get();
3,142,540✔
154
}
1,571,270✔
155

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

162
    auto iter = elf->lf_tag_defs.find(tag_name);
12,448✔
163
    std::shared_ptr<format_tag_def> retval;
12,448✔
164

165
    if (iter == elf->lf_tag_defs.end()) {
12,448✔
166
        auto tag_with_hash = fmt::format(FMT_STRING("#{}"), tag_name);
6,726✔
167
        retval = std::make_shared<format_tag_def>(tag_with_hash);
2,242✔
168
        elf->lf_tag_defs[tag_name] = retval;
2,242✔
169
    } else {
2,242✔
170
        retval = iter->second;
10,206✔
171
    }
172

173
    return retval.get();
24,896✔
174
}
12,448✔
175

176
static format_partition_def*
177
format_partition_def_provider(const yajlpp_provider_context& ypc,
6,811✔
178
                              external_log_format* elf)
179
{
180
    const intern_string_t partition_name = ypc.get_substr_i(0);
6,811✔
181

182
    auto iter = elf->lf_partition_defs.find(partition_name);
6,811✔
183
    std::shared_ptr<format_partition_def> retval;
6,811✔
184

185
    if (iter == elf->lf_partition_defs.end()) {
6,811✔
186
        retval = std::make_shared<format_partition_def>(
1,618✔
187
            partition_name.to_string());
2,427✔
188
        elf->lf_partition_defs[partition_name] = retval;
809✔
189
    } else {
190
        retval = iter->second;
6,002✔
191
    }
192

193
    return retval.get();
13,622✔
194
}
6,811✔
195

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

203
    return &retval;
5,720✔
204
}
205

206
static external_log_format::json_format_element&
207
ensure_json_format_element(external_log_format* elf, int index)
171,002✔
208
{
209
    elf->jlf_line_format.resize(index + 1);
171,002✔
210

211
    return elf->jlf_line_format[index];
171,002✔
212
}
213

214
static external_log_format::json_format_element*
215
line_format_provider(const yajlpp_provider_context& ypc,
138,556✔
216
                     external_log_format* elf)
217
{
218
    auto& jfe = ensure_json_format_element(elf, ypc.ypc_index);
138,556✔
219

220
    jfe.jfe_type = external_log_format::json_log_field::VARIABLE;
138,556✔
221

222
    return &jfe;
138,556✔
223
}
224

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

231
    if (field_name == "convert-to-local-time") {
3,429✔
232
        elf->lf_date_time.dts_local_time = val;
×
233
    } else if (field_name == "json") {
3,429✔
234
        if (val) {
3,429✔
235
            elf->elf_type = external_log_format::elf_type_t::ELF_TYPE_JSON;
2,714✔
236
        }
237
    }
238

239
    return 1;
3,429✔
240
}
3,429✔
241

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

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

264
    return 1;
1✔
265
}
1✔
266

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

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

289
    return 1;
809✔
290
}
809✔
291

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

304
    if (field_name == "timestamp-format") {
90,611✔
305
        elf->lf_timestamp_format.push_back(intern_string::lookup(value)->get());
8,772✔
306
    } else if (field_name == "module-field") {
81,839✔
307
        elf->elf_module_id_field = intern_string::lookup(value);
1,431✔
308
        elf->elf_container = true;
1,431✔
309
    }
310

311
    return 1;
90,611✔
312
}
90,611✔
313

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

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

341
    return 1;
80,288✔
342
}
80,288✔
343

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

351
    elf->elf_level_pairs.emplace_back(val, level);
12,343✔
352

353
    return 1;
12,343✔
354
}
12,343✔
355

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

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

372
    return 1;
715✔
373
}
715✔
374

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

382
    elf->lf_action_defs[action_name].ad_capture_output = val;
715✔
383

384
    return 1;
715✔
385
}
715✔
386

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

398
    elf->lf_action_defs[action_name].ad_name = action_name;
715✔
399
    elf->lf_action_defs[action_name].ad_cmdline.push_back(val);
715✔
400

401
    return 1;
715✔
402
}
715✔
403

404
static external_log_format::sample_t&
405
ensure_sample(external_log_format* elf, int index)
187,661✔
406
{
407
    elf->elf_samples.resize(index + 1);
187,661✔
408

409
    return elf->elf_samples[index];
187,661✔
410
}
411

412
static external_log_format::sample_t*
413
sample_provider(const yajlpp_provider_context& ypc, external_log_format* elf)
187,661✔
414
{
415
    auto& sample = ensure_sample(elf, ypc.ypc_index);
187,661✔
416

417
    return &sample;
187,661✔
418
}
419

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

429
    ypc->ypc_array_index.back() += 1;
32,446✔
430
    auto& jfe = ensure_json_format_element(elf, ypc->ypc_array_index.back());
32,446✔
431
    jfe.jfe_type = external_log_format::json_log_field::CONSTANT;
32,446✔
432
    jfe.jfe_default_value = val;
32,446✔
433

434
    return 1;
32,446✔
435
}
32,446✔
436

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

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

457
    json_path_handler_base::ENUM_TERMINATOR,
458
};
459

460
static constexpr json_path_handler_base::enum_value_t ALIGN_ENUM[] = {
461
    {"left"_frag, external_log_format::json_format_element::align_t::LEFT},
462
    {"right"_frag, external_log_format::json_format_element::align_t::RIGHT},
463

464
    json_path_handler_base::ENUM_TERMINATOR,
465
};
466

467
static constexpr json_path_handler_base::enum_value_t OVERFLOW_ENUM[] = {
468
    {"abbrev"_frag,
469
     external_log_format::json_format_element::overflow_t::ABBREV},
470
    {"truncate"_frag,
471
     external_log_format::json_format_element::overflow_t::TRUNCATE},
472
    {"dot-dot"_frag,
473
     external_log_format::json_format_element::overflow_t::DOTDOT},
474
    {"last-word"_frag,
475
     external_log_format::json_format_element::overflow_t::LASTWORD},
476

477
    json_path_handler_base::ENUM_TERMINATOR,
478
};
479

480
static constexpr json_path_handler_base::enum_value_t TRANSFORM_ENUM[] = {
481
    {"none"_frag, external_log_format::json_format_element::transform_t::NONE},
482
    {"uppercase"_frag,
483
     external_log_format::json_format_element::transform_t::UPPERCASE},
484
    {"lowercase"_frag,
485
     external_log_format::json_format_element::transform_t::LOWERCASE},
486
    {"capitalize"_frag,
487
     external_log_format::json_format_element::transform_t::CAPITALIZE},
488

489
    json_path_handler_base::ENUM_TERMINATOR,
490
};
491

492
static const json_path_container line_format_handlers = {
493
    yajlpp::property_handler("field")
494
        .with_synopsis("<field-name>")
495
        .with_description(
496
            "The name of the field to substitute at this position")
497
        .for_field(&external_log_format::json_format_element::jfe_value),
498

499
    yajlpp::property_handler("default-value")
500
        .with_synopsis("<string>")
501
        .with_description(
502
            "The default value for this position if the field is null")
503
        .for_field(
504
            &external_log_format::json_format_element::jfe_default_value),
505

506
    yajlpp::property_handler("timestamp-format")
507
        .with_synopsis("<string>")
508
        .with_min_length(1)
509
        .with_description("The strftime(3) format for this field")
510
        .for_field(&external_log_format::json_format_element::jfe_ts_format),
511

512
    yajlpp::property_handler("min-width")
513
        .with_min_value(0)
514
        .with_synopsis("<size>")
515
        .with_description("The minimum width of the field")
516
        .for_field(&external_log_format::json_format_element::jfe_min_width),
517

518
    yajlpp::property_handler("auto-width")
519
        .with_description("Automatically detect the necessary width of the "
520
                          "field based on the observed values")
521
        .for_field(&external_log_format::json_format_element::jfe_auto_width),
522

523
    yajlpp::property_handler("max-width")
524
        .with_min_value(0)
525
        .with_synopsis("<size>")
526
        .with_description("The maximum width of the field")
527
        .for_field(&external_log_format::json_format_element::jfe_max_width),
528

529
    yajlpp::property_handler("align")
530
        .with_synopsis("left|right")
531
        .with_description(
532
            "Align the text in the column to the left or right side")
533
        .with_enum_values(ALIGN_ENUM)
534
        .for_field(&external_log_format::json_format_element::jfe_align),
535

536
    yajlpp::property_handler("overflow")
537
        .with_synopsis("abbrev|truncate|dot-dot")
538
        .with_description("Overflow style")
539
        .with_enum_values(OVERFLOW_ENUM)
540
        .for_field(&external_log_format::json_format_element::jfe_overflow),
541

542
    yajlpp::property_handler("text-transform")
543
        .with_synopsis("none|uppercase|lowercase|capitalize")
544
        .with_description("Text transformation")
545
        .with_enum_values(TRANSFORM_ENUM)
546
        .for_field(
547
            &external_log_format::json_format_element::jfe_text_transform),
548

549
    yajlpp::property_handler("prefix")
550
        .with_synopsis("<str>")
551
        .with_description("Text to prepend to the value")
552
        .for_field(&external_log_format::json_format_element::jfe_prefix),
553

554
    yajlpp::property_handler("suffix")
555
        .with_synopsis("<str>")
556
        .with_description("Text to append to the value")
557
        .for_field(&external_log_format::json_format_element::jfe_suffix),
558
};
559

560
static constexpr json_path_handler_base::enum_value_t KIND_ENUM[] = {
561
    {"string"_frag, value_kind_t::VALUE_TEXT},
562
    {"integer"_frag, value_kind_t::VALUE_INTEGER},
563
    {"float"_frag, value_kind_t::VALUE_FLOAT},
564
    {"boolean"_frag, value_kind_t::VALUE_BOOLEAN},
565
    {"json"_frag, value_kind_t::VALUE_JSON},
566
    {"struct"_frag, value_kind_t::VALUE_STRUCT},
567
    {"quoted"_frag, value_kind_t::VALUE_QUOTED},
568
    {"xml"_frag, value_kind_t::VALUE_XML},
569

570
    json_path_handler_base::ENUM_TERMINATOR,
571
};
572

573
static constexpr json_path_handler_base::enum_value_t SCALE_OP_ENUM[] = {
574
    {"identity"_frag, scale_op_t::SO_IDENTITY},
575
    {"multiply"_frag, scale_op_t::SO_MULTIPLY},
576
    {"divide"_frag, scale_op_t::SO_DIVIDE},
577

578
    json_path_handler_base::ENUM_TERMINATOR,
579
};
580

581
static const struct json_path_container scaling_factor_handlers = {
582
    yajlpp::property_handler("op")
583
        .with_enum_values(SCALE_OP_ENUM)
584
        .for_field(&scaling_factor::sf_op),
585

586
    yajlpp::property_handler("value").for_field(&scaling_factor::sf_value),
587
};
588

589
static const struct json_path_container scale_handlers = {
590
    yajlpp::pattern_property_handler("(?<scale>[^/]+)")
591
        .with_obj_provider(scaling_factor_provider)
592
        .with_children(scaling_factor_handlers),
593
};
594

595
static const struct json_path_container unit_handlers = {
596
    yajlpp::property_handler("field")
597
        .with_synopsis("<field-name>")
598
        .with_description(
599
            "The name of the field that contains the units for this field")
600
        .for_field(&external_log_format::value_def::vd_unit_field),
601

602
    yajlpp::property_handler("scaling-factor")
603
        .with_description("Transforms the numeric value by the given factor")
604
        .with_children(scale_handlers),
605
};
606

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

615
    yajlpp::property_handler("collate")
616
        .with_synopsis("<function>")
617
        .with_description("The collating function to use for this column")
618
        .for_field(&external_log_format::value_def::vd_collate),
619

620
    yajlpp::property_handler("unit")
621
        .with_description("Unit definitions for this field")
622
        .with_children(unit_handlers),
623

624
    yajlpp::property_handler("identifier")
625
        .with_synopsis("<bool>")
626
        .with_description("Indicates whether or not this field contains an "
627
                          "identifier that should be highlighted")
628
        .for_field(&external_log_format::value_def::vd_meta,
629
                   &logline_value_meta::lvm_identifier),
630

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

638
    yajlpp::property_handler("hidden")
639
        .with_synopsis("<bool>")
640
        .with_description(
641
            "Indicates whether or not this field should be hidden")
642
        .for_field(&external_log_format::value_def::vd_meta,
643
                   &logline_value_meta::lvm_hidden),
644

645
    yajlpp::property_handler("action-list#")
646
        .with_synopsis("<string>")
647
        .with_description("Actions to execute when this field is clicked on")
648
        .for_field(&external_log_format::value_def::vd_action_list),
649

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

659
    yajlpp::property_handler("description")
660
        .with_synopsis("<string>")
661
        .with_description("A description of the field")
662
        .for_field(&external_log_format::value_def::vd_description),
663
};
664

665
static const struct json_path_container highlighter_def_handlers = {
666
    yajlpp::property_handler("pattern")
667
        .with_synopsis("<regex>")
668
        .with_description(
669
            "A regular expression to highlight in logs of this format.")
670
        .for_field(&external_log_format::highlighter_def::hd_pattern),
671

672
    yajlpp::property_handler("color")
673
        .with_synopsis("#<hex>|<name>")
674
        .with_description("The color to use when highlighting this pattern.")
675
        .for_field(&external_log_format::highlighter_def::hd_color),
676

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

683
    yajlpp::property_handler("underline")
684
        .with_synopsis("<enabled>")
685
        .with_description("Highlight this pattern with an underline.")
686
        .for_field(&external_log_format::highlighter_def::hd_underline),
687

688
    yajlpp::property_handler("blink")
689
        .with_synopsis("<enabled>")
690
        .with_description("Highlight this pattern by blinking.")
691
        .for_field(&external_log_format::highlighter_def::hd_blink),
692
};
693

694
static const json_path_handler_base::enum_value_t LEVEL_ENUM[] = {
695
    {level_names[LEVEL_TRACE], LEVEL_TRACE},
696
    {level_names[LEVEL_DEBUG5], LEVEL_DEBUG5},
697
    {level_names[LEVEL_DEBUG4], LEVEL_DEBUG4},
698
    {level_names[LEVEL_DEBUG3], LEVEL_DEBUG3},
699
    {level_names[LEVEL_DEBUG2], LEVEL_DEBUG2},
700
    {level_names[LEVEL_DEBUG], LEVEL_DEBUG},
701
    {level_names[LEVEL_INFO], LEVEL_INFO},
702
    {level_names[LEVEL_STATS], LEVEL_STATS},
703
    {level_names[LEVEL_NOTICE], LEVEL_NOTICE},
704
    {level_names[LEVEL_WARNING], LEVEL_WARNING},
705
    {level_names[LEVEL_ERROR], LEVEL_ERROR},
706
    {level_names[LEVEL_CRITICAL], LEVEL_CRITICAL},
707
    {level_names[LEVEL_FATAL], LEVEL_FATAL},
708

709
    json_path_handler_base::ENUM_TERMINATOR,
710
};
711

712
static const struct json_path_container sample_handlers = {
713
    yajlpp::property_handler("description")
714
        .with_synopsis("<text>")
715
        .with_description("A description of this sample.")
716
        .for_field(&external_log_format::sample_t::s_description),
717
    yajlpp::property_handler("line")
718
        .with_synopsis("<log-line>")
719
        .with_description(
720
            "A sample log line that should match a pattern in this format.")
721
        .for_field(&external_log_format::sample_t::s_line),
722

723
    yajlpp::property_handler("level")
724
        .with_enum_values(LEVEL_ENUM)
725
        .with_description("The expected level for this sample log line.")
726
        .for_field(&external_log_format::sample_t::s_level),
727
};
728

729
static constexpr json_path_handler_base::enum_value_t TYPE_ENUM[] = {
730
    {"text"_frag, external_log_format::elf_type_t::ELF_TYPE_TEXT},
731
    {"json"_frag, external_log_format::elf_type_t::ELF_TYPE_JSON},
732
    {"csv"_frag, external_log_format::elf_type_t::ELF_TYPE_CSV},
733

734
    json_path_handler_base::ENUM_TERMINATOR,
735
};
736

737
static const struct json_path_container regex_handlers = {
738
    yajlpp::pattern_property_handler(R"((?<pattern_name>[^/]+))")
739
        .with_description("The set of patterns used to match log messages")
740
        .with_obj_provider(pattern_provider)
741
        .with_children(pattern_handlers),
742
};
743

744
static const struct json_path_container level_handlers = {
745
    yajlpp::pattern_property_handler("(?<level>trace|debug[2345]?|info|stats|"
746
                                     "notice|warning|error|critical|fatal)")
747
        .add_cb(read_levels)
748
        .add_cb(read_level_int)
749
        .with_synopsis("<pattern|integer>")
750
        .with_description("The regular expression used to match the log text "
751
                          "for this level.  "
752
                          "For JSON logs with numeric levels, this should be "
753
                          "the number for the corresponding level."),
754
};
755

756
static const struct json_path_container value_handlers = {
757
    yajlpp::pattern_property_handler("(?<value_name>[^/]+)")
758
        .with_description(
759
            "The set of values captured by the log message patterns")
760
        .with_obj_provider(value_def_provider)
761
        .with_children(value_def_handlers),
762
};
763

764
static const struct json_path_container tag_path_handlers = {
765
    yajlpp::property_handler("glob")
766
        .with_synopsis("<glob>")
767
        .with_description("The glob to match against file paths")
768
        .with_example("*/system.log*"_frag)
769
        .for_field(&format_tag_def::path_restriction::p_glob),
770
};
771

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

794
static const struct json_path_container tag_handlers = {
795
    yajlpp::pattern_property_handler(R"((?<tag_name>[\w:;\._\-]+))")
796
        .with_description("The name of the tag to apply")
797
        .with_obj_provider(format_tag_def_provider)
798
        .with_children(format_tag_def_handlers),
799
};
800

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

823
static const struct json_path_container partition_handlers = {
824
    yajlpp::pattern_property_handler(R"((?<partition_type>[\w:;\._\-]+))")
825
        .with_description("The type of partition to apply")
826
        .with_obj_provider(format_partition_def_provider)
827
        .with_children(format_partition_def_handlers),
828
};
829

830
static const struct json_path_container highlight_handlers = {
831
    yajlpp::pattern_property_handler(R"((?<highlight_name>[^/]+))")
832
        .with_description("The definition of a highlight")
833
        .with_obj_provider<external_log_format::highlighter_def,
834
                           external_log_format>(
835
            [](const yajlpp_provider_context& ypc, external_log_format* root) {
1,605✔
836
                auto* retval
837
                    = &(root->elf_highlighter_patterns[ypc.get_substr_i(0)]);
1,605✔
838

839
                return retval;
1,605✔
840
            })
841
        .with_children(highlighter_def_handlers),
842
};
843

844
static const struct json_path_container action_def_handlers = {
845
    json_path_handler("label", read_action_def),
846
    json_path_handler("capture-output", read_action_bool),
847
    json_path_handler("cmd#", read_action_cmd),
848
};
849

850
static const struct json_path_container action_handlers = {
851
    json_path_handler(
852
        lnav::pcre2pp::code::from_const("(?<action_name>\\w+)").to_shared(),
853
        read_action_def)
854
        .with_children(action_def_handlers),
855
};
856

857
static const struct json_path_container search_table_def_handlers = {
858
    json_path_handler("pattern")
859
        .with_synopsis("<regex>")
860
        .with_description("The regular expression for this search table.")
861
        .for_field(&external_log_format::search_table_def::std_pattern),
862
    json_path_handler("glob")
863
        .with_synopsis("<glob>")
864
        .with_description("Glob pattern used to constrain hits to messages "
865
                          "that match the given pattern.")
866
        .for_field(&external_log_format::search_table_def::std_glob),
867
    json_path_handler("level")
868
        .with_synopsis("<log-level>")
869
        .with_description("Constrain hits to log messages with this level")
870
        .with_enum_values(LEVEL_ENUM)
871
        .for_field(&external_log_format::search_table_def::std_level),
872
};
873

874
static const struct json_path_container search_table_handlers = {
875
    yajlpp::pattern_property_handler("(?<table_name>\\w+)")
876
        .with_description(
877
            "The set of search tables to be automatically defined")
878
        .with_obj_provider<external_log_format::search_table_def,
879
                           external_log_format>(
880
            [](const yajlpp_provider_context& ypc, external_log_format* root) {
30,748✔
881
                auto* retval = &(root->elf_search_tables[ypc.get_substr_i(0)]);
30,748✔
882

883
                return retval;
30,748✔
884
            })
885
        .with_children(search_table_def_handlers),
886
};
887

888
static const struct json_path_container header_expr_handlers = {
889
    yajlpp::pattern_property_handler(R"((?<header_expr_name>\w+))")
890
        .with_description("SQLite expression")
891
        .for_field(&external_log_format::header_exprs::he_exprs),
892
};
893

894
static const struct json_path_container header_handlers = {
895
    yajlpp::property_handler("expr")
896
        .with_description("The expressions used to check if a file header "
897
                          "matches this file format")
898
        .for_child(&external_log_format::header::h_exprs)
899
        .with_children(header_expr_handlers),
900
    yajlpp::property_handler("size")
901
        .with_description("The minimum size required for this header type")
902
        .for_field(&external_log_format::header::h_size),
903
};
904

905
static const struct json_path_container converter_handlers = {
906
    yajlpp::property_handler("type")
907
        .with_description("The MIME type")
908
        .for_field(&external_log_format::converter::c_type),
909
    yajlpp::property_handler("header")
910
        .with_description("File header detection definitions")
911
        .for_child(&external_log_format::converter::c_header)
912
        .with_children(header_handlers),
913
    yajlpp::property_handler("command")
914
        .with_description("The script used to convert the file")
915
        .with_pattern(R"([\w\.\-]+)")
916
        .for_field(&external_log_format::converter::c_command),
917
};
918

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

942
static const struct json_path_container opid_description_format_handlers = {
943
    yajlpp::property_handler("format#")
944
        .with_description("Defines the elements of this operation description")
945
        .for_field(&log_format::opid_descriptors::od_descriptors)
946
        .with_children(opid_descriptor_handlers),
947
};
948

949
static const struct json_path_container opid_description_handlers = {
950
    yajlpp::pattern_property_handler(R"((?<opid_descriptor>[\w\.\-]+))")
951
        .with_description("A type of description for this operation")
952
        .for_field(&log_format::lf_opid_description_def)
953
        .with_children(opid_description_format_handlers),
954
};
955

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

963
static const struct json_path_container opid_handlers = {
964
    yajlpp::property_handler("subid")
965
        .with_description("The field that holds the ID for a sub-operation")
966
        .for_field(&external_log_format::elf_subid_field),
967
    yajlpp::property_handler("description")
968
        .with_description(
969
            "Define how to construct a description of an operation")
970
        .with_children(opid_description_handlers),
971
    yajlpp::property_handler("sub-description")
972
        .with_description(
973
            "Define how to construct a description of a sub-operation")
974
        .with_children(subid_description_handlers),
975
};
976

977
const struct json_path_container format_handlers = {
978
    yajlpp::property_handler("regex")
979
        .with_description(
980
            "The set of regular expressions used to match log messages")
981
        .with_children(regex_handlers),
982

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

1085
    yajlpp::property_handler("value")
1086
        .with_description("The set of value definitions")
1087
        .with_children(value_handlers),
1088

1089
    yajlpp::property_handler("tags")
1090
        .with_description("The tags to automatically apply to log messages")
1091
        .with_children(tag_handlers),
1092

1093
    yajlpp::property_handler("partitions")
1094
        .with_description(
1095
            "The partitions to automatically apply to log messages")
1096
        .with_children(partition_handlers),
1097

1098
    yajlpp::property_handler("action").with_children(action_handlers),
1099
    yajlpp::property_handler("sample#")
1100
        .with_description("An array of sample log messages to be tested "
1101
                          "against the log message patterns")
1102
        .with_obj_provider(sample_provider)
1103
        .with_children(sample_handlers),
1104

1105
    yajlpp::property_handler("line-format#")
1106
        .with_description("The display format for JSON-encoded log messages")
1107
        .with_obj_provider(line_format_provider)
1108
        .add_cb(read_json_constant)
1109
        .with_children(line_format_handlers),
1110
    json_path_handler("search-table")
1111
        .with_description(
1112
            "Search tables to automatically define for this log format")
1113
        .with_children(search_table_handlers),
1114

1115
    yajlpp::property_handler("highlights")
1116
        .with_description("The set of highlight definitions")
1117
        .with_children(highlight_handlers),
1118

1119
    yajlpp::property_handler("file-type")
1120
        .with_synopsis("text|json|csv")
1121
        .with_description("The type of file that contains the log messages")
1122
        .with_enum_values(TYPE_ENUM)
1123
        .for_field(&external_log_format::elf_type),
1124

1125
    yajlpp::property_handler("max-unrecognized-lines")
1126
        .with_synopsis("<lines>")
1127
        .with_description("The maximum number of lines in a file to use when "
1128
                          "detecting the format")
1129
        .with_min_value(1)
1130
        .for_field(&log_format::lf_max_unrecognized_lines),
1131
};
1132

1133
static int
1134
read_id(yajlpp_parse_context* ypc,
48,237✔
1135
        const unsigned char* str,
1136
        size_t len,
1137
        yajl_string_props_t*)
1138
{
1139
    auto* ud = static_cast<loader_userdata*>(ypc->ypc_userdata);
48,237✔
1140
    auto file_id = std::string((const char*) str, len);
48,237✔
1141

1142
    ud->ud_file_schema = file_id;
48,237✔
1143
    if (SUPPORTED_FORMAT_SCHEMAS.find(file_id)
48,237✔
1144
        == SUPPORTED_FORMAT_SCHEMAS.end())
96,474✔
1145
    {
1146
        const auto* handler = ypc->ypc_current_handler;
1✔
1147
        attr_line_t notes{"expecting one of the following $schema values:"};
1✔
1148

1149
        for (const auto& schema : SUPPORTED_FORMAT_SCHEMAS) {
2✔
1150
            notes.append("\n").append(
2✔
1151
                lnav::roles::symbol(fmt::format(FMT_STRING("  {}"), schema)));
5✔
1152
        }
1153
        ypc->report_error(
2✔
UNCOV
1154
            lnav::console::user_message::error(
×
1155
                attr_line_t("'")
1✔
1156
                    .append(lnav::roles::symbol(file_id))
2✔
1157
                    .append("' is not a supported log format $schema version"))
1✔
1158
                .with_snippet(ypc->get_snippet())
2✔
1159
                .with_note(notes)
1✔
1160
                .with_help(handler->get_help_text(ypc)));
1✔
1161
    }
1✔
1162

1163
    return 1;
48,237✔
1164
}
48,237✔
1165

1166
const struct json_path_container root_format_handler = json_path_container{
1167
    json_path_handler("$schema", read_id)
1168
        .with_synopsis("The URI of the schema for this file")
1169
        .with_description("Specifies the type of this file"),
1170

1171
    yajlpp::pattern_property_handler("(?<format_name>\\w+)")
1172
        .with_description("The definition of a log file format.")
1173
        .with_obj_provider(ensure_format)
1174
        .with_children(format_handlers),
1175
}
1176
    .with_schema_id(DEFAULT_FORMAT_SCHEMA);
1177

1178
static void
1179
write_sample_file()
715✔
1180
{
1181
    const auto dstdir = lnav::paths::dotlnav();
715✔
1182
    for (const auto& bsf : lnav_format_json) {
47,905✔
1183
        auto sample_path = dstdir
1184
            / fmt::format(FMT_STRING("formats/default/{}.sample"),
188,760✔
1185
                          bsf.get_name());
94,380✔
1186

1187
        auto stat_res = lnav::filesystem::stat_file(sample_path);
47,190✔
1188
        if (stat_res.isOk()) {
47,190✔
1189
            auto st = stat_res.unwrap();
40,260✔
1190
            if (st.st_mtime >= lnav::filesystem::self_mtime()) {
40,260✔
1191
                log_debug("skipping writing sample: %s (mtimes %d >= %d)",
40,260✔
1192
                          bsf.get_name(),
1193
                          st.st_mtime,
1194
                          lnav::filesystem::self_mtime());
1195
                continue;
40,260✔
1196
            }
UNCOV
1197
            log_debug("sample file needs to be updated: %s", bsf.get_name());
×
1198
        } else {
1199
            log_debug("sample file does not exist: %s", bsf.get_name());
6,930✔
1200
        }
1201

1202
        auto sfp = bsf.to_string_fragment_producer();
6,930✔
1203
        auto write_res = lnav::filesystem::write_file(
1204
            sample_path,
1205
            *sfp,
6,930✔
1206
            {lnav::filesystem::write_file_options::read_only});
20,790✔
1207

1208
        if (write_res.isErr()) {
6,930✔
1209
            auto msg = write_res.unwrapErr();
4,488✔
1210
            fprintf(stderr,
4,488✔
1211
                    "error:unable to write default format file: %s -- %s\n",
1212
                    sample_path.c_str(),
1213
                    msg.c_str());
1214
        }
4,488✔
1215
    }
87,450✔
1216

1217
    for (const auto& bsf : lnav_sh_scripts) {
3,575✔
1218
        auto sh_path = dstdir
1219
            / fmt::format(FMT_STRING("formats/default/{}"), bsf.get_name());
11,440✔
1220
        auto stat_res = lnav::filesystem::stat_file(sh_path);
2,860✔
1221
        if (stat_res.isOk()) {
2,860✔
1222
            auto st = stat_res.unwrap();
2,440✔
1223
            if (st.st_mtime >= lnav::filesystem::self_mtime()) {
2,440✔
1224
                continue;
2,440✔
1225
            }
1226
        }
1227

1228
        auto sfp = bsf.to_string_fragment_producer();
420✔
1229
        auto write_res = lnav::filesystem::write_file(
1230
            sh_path,
1231
            *sfp,
420✔
1232
            {
1233
                lnav::filesystem::write_file_options::executable,
1234
                lnav::filesystem::write_file_options::read_only,
1235
            });
1,260✔
1236

1237
        if (write_res.isErr()) {
420✔
1238
            auto msg = write_res.unwrapErr();
272✔
1239
            fprintf(stderr,
272✔
1240
                    "error:unable to write default text file: %s -- %s\n",
1241
                    sh_path.c_str(),
1242
                    msg.c_str());
1243
        }
272✔
1244
    }
5,300✔
1245

1246
    for (const auto& bsf : lnav_scripts) {
12,870✔
1247
        script_metadata meta;
12,155✔
1248
        auto sf = bsf.to_string_fragment_producer()->to_string();
12,155✔
1249

1250
        meta.sm_name = bsf.get_name().to_string();
12,155✔
1251
        extract_metadata(sf, meta);
12,155✔
1252
        auto path
1253
            = fmt::format(FMT_STRING("formats/default/{}.lnav"), meta.sm_name);
36,465✔
1254
        auto script_path = dstdir / path;
12,155✔
1255
        auto stat_res = lnav::filesystem::stat_file(script_path);
12,155✔
1256
        if (stat_res.isOk()) {
12,155✔
1257
            auto st = stat_res.unwrap();
10,407✔
1258
            if (st.st_mtime >= lnav::filesystem::self_mtime()) {
10,407✔
1259
                continue;
10,407✔
1260
            }
1261
        }
1262

1263
        auto write_res = lnav::filesystem::write_file(
1264
            script_path,
1265
            sf,
1266
            {
1267
                lnav::filesystem::write_file_options::executable,
1268
                lnav::filesystem::write_file_options::read_only,
1269
            });
3,496✔
1270
        if (write_res.isErr()) {
1,748✔
1271
            fprintf(stderr,
1,156✔
1272
                    "error:unable to write default script file: %s -- %s\n",
1273
                    script_path.c_str(),
1274
                    strerror(errno));
1,156✔
1275
        }
1276
    }
53,783✔
1277
}
715✔
1278

1279
static void
1280
format_error_reporter(const yajlpp_parse_context& ypc,
16✔
1281
                      const lnav::console::user_message& msg)
1282
{
1283
    auto* ud = (loader_userdata*) ypc.ypc_userdata;
16✔
1284

1285
    ud->ud_errors->emplace_back(msg);
16✔
1286
}
16✔
1287

1288
std::vector<intern_string_t>
1289
load_format_file(const std::filesystem::path& filename,
1,049✔
1290
                 std::vector<lnav::console::user_message>& errors)
1291
{
1292
    std::vector<intern_string_t> retval;
1,049✔
1293
    loader_userdata ud;
1,049✔
1294
    auto_fd fd;
1,049✔
1295

1296
    log_info("loading formats from file: %s", filename.c_str());
1,049✔
1297
    yajlpp_parse_context ypc(intern_string::lookup(filename.string()),
2,098✔
1298
                             &root_format_handler);
1,049✔
1299
    ud.ud_parse_context = &ypc;
1,049✔
1300
    ud.ud_format_path = filename;
1,049✔
1301
    ud.ud_format_names = &retval;
1,049✔
1302
    ud.ud_errors = &errors;
1,049✔
1303
    ypc.ypc_userdata = &ud;
1,049✔
1304
    ypc.with_obj(ud);
1,049✔
1305
    if ((fd = lnav::filesystem::openp(filename, O_RDONLY)) == -1) {
1,049✔
1306
        errors.emplace_back(
×
1307
            lnav::console::user_message::error(
×
1308
                attr_line_t("unable to open format file: ")
×
UNCOV
1309
                    .append(lnav::roles::file(filename.string())))
×
1310
                .with_errno_reason());
1311
    } else {
1312
        auto_mem<yajl_handle_t> handle(yajl_free);
1,049✔
1313
        char buffer[2048];
1314
        off_t offset = 0;
1,049✔
1315
        ssize_t rc = -1;
1,049✔
1316

1317
        handle = yajl_alloc(&ypc.ypc_callbacks, nullptr, &ypc);
1,049✔
1318
        ypc.with_handle(handle).with_error_reporter(format_error_reporter);
1,049✔
1319
        yajl_config(handle, yajl_allow_comments, 1);
1,049✔
1320
        while (true) {
1321
            rc = read(fd, buffer, sizeof(buffer));
2,285✔
1322
            if (rc == 0) {
2,285✔
1323
                break;
1,048✔
1324
            }
1325
            if (rc == -1) {
1,237✔
1326
                errors.emplace_back(
×
1327
                    lnav::console::user_message::error(
×
1328
                        attr_line_t("unable to read format file: ")
×
UNCOV
1329
                            .append(lnav::roles::file(filename.string())))
×
1330
                        .with_errno_reason());
UNCOV
1331
                break;
×
1332
            }
1333
            if (offset == 0 && (rc > 2) && (buffer[0] == '#')
1,237✔
UNCOV
1334
                && (buffer[1] == '!'))
×
1335
            {
1336
                // Turn it into a JavaScript comment.
UNCOV
1337
                buffer[0] = buffer[1] = '/';
×
1338
            }
1339
            if (ypc.parse((const unsigned char*) buffer, rc) != yajl_status_ok)
1,237✔
1340
            {
1341
                break;
1✔
1342
            }
1343
            offset += rc;
1,236✔
1344
        }
1345
        if (rc == 0) {
1,049✔
1346
            ypc.complete_parse();
1,048✔
1347
        }
1348

1349
        if (ud.ud_file_schema.empty()) {
1,049✔
1350
            static const auto SCHEMA_LINE
1351
                = attr_line_t()
2✔
1352
                      .append(
4✔
1353
                          fmt::format(FMT_STRING("    \"$schema\": \"{}\","),
8✔
1354
                                      *SUPPORTED_FORMAT_SCHEMAS.begin()))
4✔
1355
                      .with_attr_for_all(VC_ROLE.value(role_t::VCR_QUOTED_CODE))
4✔
1356
                      .move();
4✔
1357

1358
            errors.emplace_back(
2✔
UNCOV
1359
                lnav::console::user_message::warning(
×
1360
                    attr_line_t("format file is missing ")
4✔
1361
                        .append_quoted("$schema"_symbol)
2✔
1362
                        .append(" property"))
2✔
1363
                    .with_snippet(lnav::console::snippet::from(
6✔
1364
                        intern_string::lookup(filename.string()), ""))
4✔
1365
                    .with_note("the schema specifies the supported format "
4✔
1366
                               "version and can be used with editors to "
1367
                               "automatically validate the file")
1368
                    .with_help(attr_line_t("add the following property to the "
4✔
1369
                                           "top-level JSON object:\n")
1370
                                   .append(SCHEMA_LINE)));
2✔
1371
        }
1372
    }
1,049✔
1373

1374
    return retval;
2,098✔
1375
}
1,049✔
1376

1377
static void
1378
load_from_path(const std::filesystem::path& path,
1,899✔
1379
               std::vector<lnav::console::user_message>& errors)
1380
{
1381
    auto format_path = path / "formats/*/*.json";
1,899✔
1382
    static_root_mem<glob_t, globfree> gl;
1,899✔
1383

1384
    log_info("loading formats from path: %s", format_path.c_str());
1,899✔
1385
    if (glob(format_path.c_str(), 0, nullptr, gl.inout()) == 0) {
1,899✔
1386
        for (int lpc = 0; lpc < (int) gl->gl_pathc; lpc++) {
1,142✔
1387
            auto filepath = std::filesystem::path(gl->gl_pathv[lpc]);
1,046✔
1388

1389
            if (startswith(filepath.filename().string(), "config.")) {
1,046✔
UNCOV
1390
                log_info("  not loading config as format: %s",
×
1391
                         filepath.c_str());
UNCOV
1392
                continue;
×
1393
            }
1394

1395
            auto format_list = load_format_file(filepath, errors);
1,046✔
1396
            if (format_list.empty()) {
1,046✔
1397
                log_warning("Empty format file: %s", filepath.c_str());
2✔
1398
            } else {
1399
                log_info("contents of format file '%s':", filepath.c_str());
1,044✔
1400
                for (auto iter = format_list.begin(); iter != format_list.end();
2,182✔
1401
                     ++iter)
1,138✔
1402
                {
1403
                    log_info("  found format: %s", iter->get());
1,138✔
1404
                }
1405
            }
1406
        }
1,046✔
1407
    }
1408
}
1,899✔
1409

1410
void
1411
load_formats(const std::vector<std::filesystem::path>& extra_paths,
715✔
1412
             std::vector<lnav::console::user_message>& errors)
1413
{
1414
    auto op_guard = lnav_opid_guard::once(__FUNCTION__);
715✔
1415

1416
    auto default_source = lnav::paths::dotlnav() / "default";
715✔
1417
    std::vector<intern_string_t> retval;
715✔
1418
    loader_userdata ud;
715✔
1419
    yajl_handle handle;
1420

1421
    write_sample_file();
715✔
1422

1423
    log_debug("Loading default formats");
715✔
1424
    for (const auto& bsf : lnav_format_json) {
47,905✔
1425
        yajlpp_parse_context ypc_builtin(intern_string::lookup(bsf.get_name()),
47,190✔
1426
                                         &root_format_handler);
47,190✔
1427
        handle = yajl_alloc(&ypc_builtin.ypc_callbacks, nullptr, &ypc_builtin);
47,190✔
1428
        ud.ud_parse_context = &ypc_builtin;
47,190✔
1429
        ud.ud_format_names = &retval;
47,190✔
1430
        ud.ud_errors = &errors;
47,190✔
1431
        ypc_builtin.with_obj(ud)
94,380✔
1432
            .with_handle(handle)
47,190✔
1433
            .with_error_reporter(format_error_reporter)
47,190✔
1434
            .ypc_userdata
1435
            = &ud;
94,380✔
1436
        yajl_config(handle, yajl_allow_comments, 1);
47,190✔
1437
        auto sf = bsf.to_string_fragment_producer();
47,190✔
1438
        ypc_builtin.parse(*sf);
47,190✔
1439
        yajl_free(handle);
47,190✔
1440
    }
47,190✔
1441

1442
    for (const auto& extra_path : extra_paths) {
2,614✔
1443
        load_from_path(extra_path, errors);
1,899✔
1444
    }
1445

1446
    uint8_t mod_counter = 0;
715✔
1447

1448
    std::vector<std::shared_ptr<external_log_format>> alpha_ordered_formats;
715✔
1449
    for (auto iter = LOG_FORMATS.begin(); iter != LOG_FORMATS.end(); ++iter) {
48,949✔
1450
        auto& elf = iter->second;
48,234✔
1451
        elf->build(errors);
48,234✔
1452

1453
        if (elf->elf_has_module_format) {
48,234✔
1454
            mod_counter += 1;
2,145✔
1455
            elf->lf_mod_index = mod_counter;
2,145✔
1456
        }
1457

1458
        for (auto& check_iter : LOG_FORMATS) {
3,312,024✔
1459
            if (iter->first == check_iter.first) {
3,263,790✔
1460
                continue;
48,234✔
1461
            }
1462

1463
            auto& check_elf = check_iter.second;
3,215,556✔
1464
            if (elf->match_samples(check_elf->elf_samples)) {
3,215,556✔
1465
                log_warning(
15,542✔
1466
                    "Format collision, format '%s' matches sample from '%s'",
1467
                    elf->get_name().get(),
1468
                    check_elf->get_name().get());
1469
                elf->elf_collision.push_back(check_elf->get_name());
15,542✔
1470
            }
1471
        }
1472

1473
        alpha_ordered_formats.push_back(elf);
48,234✔
1474
    }
1475

1476
    auto& graph_ordered_formats = external_log_format::GRAPH_ORDERED_FORMATS;
715✔
1477

1478
    while (!alpha_ordered_formats.empty()) {
9,151✔
1479
        std::vector<intern_string_t> popped_formats;
8,436✔
1480

1481
        for (auto iter = alpha_ordered_formats.begin();
8,436✔
1482
             iter != alpha_ordered_formats.end();)
92,994✔
1483
        {
1484
            auto elf = *iter;
84,558✔
1485
            if (elf->elf_collision.empty()) {
84,558✔
1486
                iter = alpha_ordered_formats.erase(iter);
48,234✔
1487
                popped_formats.push_back(elf->get_name());
48,234✔
1488
                graph_ordered_formats.push_back(elf);
48,234✔
1489
            } else {
1490
                ++iter;
36,324✔
1491
            }
1492
        }
84,558✔
1493

1494
        if (popped_formats.empty() && !alpha_ordered_formats.empty()) {
8,436✔
1495
            bool broke_cycle = false;
2,430✔
1496

1497
            log_warning("Detected a cycle...");
2,430✔
1498
            for (const auto& elf : alpha_ordered_formats) {
14,193✔
1499
                if (elf->elf_builtin_format) {
12,048✔
1500
                    log_warning("  Skipping builtin format -- %s",
11,763✔
1501
                                elf->get_name().get());
1502
                } else {
1503
                    log_warning("  Breaking cycle by picking -- %s",
285✔
1504
                                elf->get_name().get());
1505
                    elf->elf_collision.clear();
285✔
1506
                    broke_cycle = true;
285✔
1507
                    break;
285✔
1508
                }
1509
            }
1510
            if (!broke_cycle) {
2,430✔
1511
                alpha_ordered_formats.front()->elf_collision.clear();
2,145✔
1512
            }
1513
        }
1514

1515
        for (const auto& elf : alpha_ordered_formats) {
44,760✔
1516
            for (auto& popped_format : popped_formats) {
414,949✔
1517
                elf->elf_collision.remove(popped_format);
378,625✔
1518
            }
1519
        }
1520
    }
8,436✔
1521

1522
    log_info("Format order:") for (auto& graph_ordered_format :
1,430✔
1523
                                   graph_ordered_formats)
49,664✔
1524
    {
1525
        log_info("  %s", graph_ordered_format->get_name().get());
48,234✔
1526
    }
1527

1528
    auto& roots = log_format::get_root_formats();
715✔
1529
    auto iter = std::find_if(roots.begin(), roots.end(), [](const auto& elem) {
715✔
1530
        return elem->get_name() == "generic_log";
3,575✔
1531
    });
1532
    roots.insert(
715✔
1533
        iter, graph_ordered_formats.begin(), graph_ordered_formats.end());
1534
}
715✔
1535

1536
static void
1537
exec_sql_in_path(sqlite3* db,
1,853✔
1538
                 const std::map<std::string, scoped_value_t>& global_vars,
1539
                 const std::filesystem::path& path,
1540
                 std::vector<lnav::console::user_message>& errors)
1541
{
1542
    auto format_path = path / "formats/*/*.sql";
1,853✔
1543
    static_root_mem<glob_t, globfree> gl;
1,853✔
1544

1545
    log_info("executing SQL files in path: %s", format_path.c_str());
1,853✔
1546
    if (glob(format_path.c_str(), 0, nullptr, gl.inout()) == 0) {
1,853✔
1547
        for (int lpc = 0; lpc < (int) gl->gl_pathc; lpc++) {
1,301✔
1548
            auto filename = std::filesystem::path(gl->gl_pathv[lpc]);
651✔
1549
            auto read_res = lnav::filesystem::read_file(filename);
651✔
1550

1551
            if (read_res.isOk()) {
651✔
1552
                log_info("Executing SQL file: %s", filename.c_str());
651✔
1553
                auto content = read_res.unwrap();
651✔
1554

1555
                sql_execute_script(
651✔
1556
                    db, global_vars, filename.c_str(), content.c_str(), errors);
1557
            } else {
651✔
1558
                errors.emplace_back(
×
1559
                    lnav::console::user_message::error(
×
1560
                        attr_line_t("unable to read format file: ")
×
1561
                            .append(lnav::roles::file(filename.string())))
×
UNCOV
1562
                        .with_reason(read_res.unwrapErr()));
×
1563
            }
1564
        }
651✔
1565
    }
1566
}
1,853✔
1567

1568
void
1569
load_format_extra(sqlite3* db,
601✔
1570
                  const std::map<std::string, scoped_value_t>& global_vars,
1571
                  const std::vector<std::filesystem::path>& extra_paths,
1572
                  std::vector<lnav::console::user_message>& errors)
1573
{
1574
    for (const auto& extra_path : extra_paths) {
2,454✔
1575
        exec_sql_in_path(db, global_vars, extra_path, errors);
1,853✔
1576
    }
1577
}
601✔
1578

1579
static void
1580
extract_metadata(string_fragment contents, script_metadata& meta_out)
12,880✔
1581
{
1582
    static const auto SYNO_RE = lnav::pcre2pp::code::from_const(
1583
        "^#\\s+@synopsis:(.*)$", PCRE2_MULTILINE);
12,880✔
1584
    static const auto DESC_RE = lnav::pcre2pp::code::from_const(
1585
        "^#\\s+@description:(.*)$", PCRE2_MULTILINE);
12,880✔
1586
    static const auto OUTPUT_FORMAT_RE = lnav::pcre2pp::code::from_const(
1587
        "^#\\s+@output-format:\\s+(.*)$", PCRE2_MULTILINE);
12,880✔
1588

1589
    auto syno_md = SYNO_RE.create_match_data();
12,880✔
1590
    auto syno_match_res
1591
        = SYNO_RE.capture_from(contents).into(syno_md).matches().ignore_error();
12,880✔
1592
    if (syno_match_res) {
12,880✔
1593
        meta_out.sm_synopsis = syno_md[1]->trim().to_string();
12,849✔
1594
    }
1595
    auto desc_md = DESC_RE.create_match_data();
12,880✔
1596
    auto desc_match_res
1597
        = DESC_RE.capture_from(contents).into(desc_md).matches().ignore_error();
12,880✔
1598
    if (desc_match_res) {
12,880✔
1599
        meta_out.sm_description = desc_md[1]->trim().to_string();
12,849✔
1600
    }
1601

1602
    auto out_format_md = OUTPUT_FORMAT_RE.create_match_data();
12,880✔
1603
    auto out_format_res = OUTPUT_FORMAT_RE.capture_from(contents)
12,880✔
1604
                              .into(out_format_md)
12,880✔
1605
                              .matches()
25,760✔
1606
                              .ignore_error();
12,880✔
1607
    if (out_format_res) {
12,880✔
1608
        auto out_format_frag = out_format_md[1]->trim();
758✔
1609
        auto from_res = from<text_format_t>(out_format_frag);
758✔
1610
        if (from_res.isErr()) {
758✔
UNCOV
1611
            log_error("%s (%s): invalid @output-format '%.*s'",
×
1612
                      meta_out.sm_name.c_str(),
1613
                      meta_out.sm_path.c_str(),
1614
                      out_format_frag.length(),
1615
                      out_format_frag.data());
1616
        } else {
1617
            meta_out.sm_output_format = from_res.unwrap();
758✔
1618
            log_info("%s (%s): setting output format to %d",
758✔
1619
                     meta_out.sm_name.c_str(),
1620
                     meta_out.sm_path.c_str(),
1621
                     meta_out.sm_output_format);
1622
        }
1623
    }
758✔
1624

1625
    if (!meta_out.sm_synopsis.empty()) {
12,880✔
1626
        size_t space = meta_out.sm_synopsis.find(' ');
12,849✔
1627

1628
        if (space == std::string::npos) {
12,849✔
1629
            space = meta_out.sm_synopsis.size();
9,102✔
1630
        }
1631
        meta_out.sm_name = meta_out.sm_synopsis.substr(0, space);
12,849✔
1632
    }
1633
}
12,880✔
1634

1635
void
1636
extract_metadata_from_file(struct script_metadata& meta_inout)
725✔
1637
{
1638
    auto stat_res = lnav::filesystem::stat_file(meta_inout.sm_path);
725✔
1639
    if (stat_res.isErr()) {
725✔
UNCOV
1640
        log_warning("unable to open script: %s -- %s",
×
1641
                    meta_inout.sm_path.c_str(),
1642
                    stat_res.unwrapErr().c_str());
UNCOV
1643
        return;
×
1644
    }
1645

1646
    auto st = stat_res.unwrap();
725✔
1647
    if (!S_ISREG(st.st_mode)) {
725✔
UNCOV
1648
        log_warning("script is not a regular file -- %s",
×
1649
                    meta_inout.sm_path.c_str());
UNCOV
1650
        return;
×
1651
    }
1652

1653
    auto open_res = lnav::filesystem::open_file(meta_inout.sm_path, O_RDONLY);
725✔
1654
    if (open_res.isErr()) {
725✔
UNCOV
1655
        log_warning("unable to open script file: %s -- %s",
×
1656
                    meta_inout.sm_path.c_str(),
1657
                    open_res.unwrapErr().c_str());
UNCOV
1658
        return;
×
1659
    }
1660

1661
    auto fd = open_res.unwrap();
725✔
1662
    char buffer[8 * 1024];
1663
    auto rc = read(fd, buffer, sizeof(buffer));
725✔
1664
    if (rc > 0) {
725✔
1665
        extract_metadata(string_fragment::from_bytes(buffer, rc), meta_inout);
725✔
1666
    }
1667
}
725✔
1668

1669
static void
1670
find_format_in_path(const std::filesystem::path& path,
135✔
1671
                    available_scripts& scripts)
1672
{
1673
    for (const auto& format_path :
405✔
1674
         {path / "formats/*/*.lnav", path / "configs/*/*.lnav"})
810✔
1675
    {
1676
        static_root_mem<glob_t, globfree> gl;
270✔
1677

1678
        log_debug("Searching for script in path: %s", format_path.c_str());
270✔
1679
        if (glob(format_path.c_str(), 0, nullptr, gl.inout()) == 0) {
270✔
1680
            for (size_t lpc = 0; lpc < gl->gl_pathc; lpc++) {
773✔
1681
                const char* filename = basename(gl->gl_pathv[lpc]);
718✔
1682
                auto script_name = std::string(filename, strlen(filename) - 5);
718✔
1683
                struct script_metadata meta;
718✔
1684

1685
                meta.sm_path = gl->gl_pathv[lpc];
718✔
1686
                meta.sm_name = script_name;
718✔
1687
                extract_metadata_from_file(meta);
718✔
1688
                scripts.as_scripts[script_name].push_back(meta);
718✔
1689

1690
                log_info("  found script: %s", meta.sm_path.c_str());
718✔
1691
            }
718✔
1692
        }
1693
    }
675✔
1694
}
135✔
1695

1696
available_scripts
1697
find_format_scripts(const std::vector<std::filesystem::path>& extra_paths)
43✔
1698
{
1699
    available_scripts retval;
43✔
1700
    for (const auto& extra_path : extra_paths) {
178✔
1701
        find_format_in_path(extra_path, retval);
135✔
1702
    }
1703
    return retval;
43✔
UNCOV
1704
}
×
1705

1706
void
1707
load_format_vtabs(log_vtab_manager* vtab_manager,
601✔
1708
                  std::vector<lnav::console::user_message>& errors)
1709
{
1710
    auto& root_formats = LOG_FORMATS;
601✔
1711

1712
    for (auto& root_format : root_formats) {
40,805✔
1713
        root_format.second->register_vtabs(vtab_manager, errors);
40,204✔
1714
    }
1715
}
601✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc