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

tstack / lnav / 23346808785-2859

20 Mar 2026 02:14PM UTC coverage: 69.012% (-0.009%) from 69.021%
23346808785-2859

push

github

tstack
[tests] update expected

52736 of 76416 relevant lines covered (69.01%)

522102.57 hits per line

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

92.46
/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 "log_level.hh"
59
#include "sql_execute.hh"
60
#include "sql_util.hh"
61
#include "yajlpp/yajlpp.hh"
62
#include "yajlpp/yajlpp_def.hh"
63

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

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

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

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

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

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

95
    if (find(formats->begin(), formats->end(), name) == formats->end()) {
4,372,228✔
96
        formats->push_back(name);
57,119✔
97
    }
98

99
    if (!ud->ud_format_path.empty()) {
4,372,228✔
100
        const intern_string_t i_src_path
101
            = intern_string::lookup(ud->ud_format_path.string());
45,298✔
102
        auto srcs_iter = retval->elf_format_sources.find(i_src_path);
45,298✔
103
        if (srcs_iter == retval->elf_format_sources.end()) {
45,298✔
104
            retval->elf_format_source_order.emplace_back(ud->ud_format_path);
1,313✔
105
            retval->elf_format_sources[i_src_path]
2,626✔
106
                = ud->ud_parse_context->get_line_number();
1,313✔
107
        }
108
    }
109

110
    if (ud->ud_format_path.empty()) {
4,372,228✔
111
        retval->elf_builtin_format = true;
4,326,930✔
112
    }
113

114
    return retval;
4,372,228✔
115
}
116

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

123
    if (pat.get() == nullptr) {
311,034✔
124
        pat = std::make_shared<external_log_format::pattern>();
103,678✔
125
    }
126

127
    if (pat->p_config_path.empty()) {
311,034✔
128
        pat->p_name = intern_string::lookup(regex_name);
103,678✔
129
        pat->p_config_path = fmt::format(
207,356✔
130
            FMT_STRING("/{}/regex/{}"), elf->get_name(), regex_name);
518,390✔
131
    }
132

133
    return pat.get();
622,068✔
134
}
311,034✔
135

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

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

144
    if (iter == elf->elf_value_defs.end()) {
1,938,871✔
145
        retval = std::make_shared<external_log_format::value_def>(
490,444✔
146
            value_name,
147
            value_kind_t::VALUE_TEXT,
490,444✔
148
            logline_value_meta::external_column{},
×
149
            elf);
490,444✔
150
        elf->elf_value_defs[value_name] = retval;
490,444✔
151
        elf->elf_value_def_order.emplace_back(retval);
490,444✔
152
    } else {
153
        retval = iter->second;
1,448,427✔
154
    }
155

156
    return retval.get();
3,877,742✔
157
}
1,938,871✔
158

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

165
    auto iter = elf->lf_tag_defs.find(tag_name);
13,673✔
166
    std::shared_ptr<format_tag_def> retval;
13,673✔
167

168
    if (iter == elf->lf_tag_defs.end()) {
13,673✔
169
        auto tag_with_hash = fmt::format(FMT_STRING("#{}"), tag_name);
7,383✔
170
        retval = std::make_shared<format_tag_def>(tag_with_hash);
2,461✔
171
        elf->lf_tag_defs[tag_name] = retval;
2,461✔
172
    } else {
2,461✔
173
        retval = iter->second;
11,212✔
174
    }
175

176
    return retval.get();
27,346✔
177
}
13,673✔
178

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

185
    auto iter = elf->lf_partition_defs.find(partition_name);
9,832✔
186
    std::shared_ptr<format_partition_def> retval;
9,832✔
187

188
    if (iter == elf->lf_partition_defs.end()) {
9,832✔
189
        retval = std::make_shared<format_partition_def>(
3,344✔
190
            partition_name.to_string());
5,016✔
191
        elf->lf_partition_defs[partition_name] = retval;
1,672✔
192
    } else {
193
        retval = iter->second;
8,160✔
194
    }
195

196
    return retval.get();
19,664✔
197
}
9,832✔
198

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

206
    return &retval;
6,288✔
207
}
208

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

214
    return elf->jlf_line_format[index];
288,406✔
215
}
216

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

223
    jfe.jfe_type = external_log_format::json_log_field::VARIABLE;
244,939✔
224

225
    return &jfe;
244,939✔
226
}
227

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

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

238
    return 1;
3,749✔
239
}
3,749✔
240

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

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

263
    return 1;
1✔
264
}
1✔
265

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

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

288
    return 1;
886✔
289
}
886✔
290

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

303
    if (field_name == "timestamp-format") {
106,737✔
304
        elf->lf_timestamp_format.push_back(intern_string::lookup(value)->get());
11,994✔
305
    }
306

307
    return 1;
106,737✔
308
}
106,737✔
309

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

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

337
    return 1;
103,897✔
338
}
103,897✔
339

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

347
    elf->elf_level_pairs.emplace_back(val, level);
13,562✔
348

349
    return 1;
13,562✔
350
}
13,562✔
351

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

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

368
    return 1;
786✔
369
}
786✔
370

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

378
    elf->lf_action_defs[action_name].ad_capture_output = val;
786✔
379

380
    return 1;
786✔
381
}
786✔
382

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

394
    elf->lf_action_defs[action_name].ad_name = action_name;
786✔
395
    elf->lf_action_defs[action_name].ad_cmdline.push_back(val);
786✔
396

397
    return 1;
786✔
398
}
786✔
399

400
static external_log_format::sample_t&
401
ensure_sample(external_log_format* elf, int index)
229,938✔
402
{
403
    elf->elf_samples.resize(index + 1);
229,938✔
404

405
    return elf->elf_samples[index];
229,938✔
406
}
407

408
static external_log_format::sample_t*
409
sample_provider(const yajlpp_provider_context& ypc, external_log_format* elf)
229,938✔
410
{
411
    auto& sample = ensure_sample(elf, ypc.ypc_index);
229,938✔
412

413
    return &sample;
229,938✔
414
}
415

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

425
    ypc->ypc_array_index.back() += 1;
43,467✔
426
    auto& jfe = ensure_json_format_element(elf, ypc->ypc_array_index.back());
43,467✔
427
    jfe.jfe_type = external_log_format::json_log_field::CONSTANT;
43,467✔
428
    jfe.jfe_default_value = val;
43,467✔
429

430
    return 1;
43,467✔
431
}
43,467✔
432

433
static const struct json_path_container pattern_handlers = {
434
    yajlpp::property_handler("pattern")
435
        .with_synopsis("<message-regex>")
436
        .with_description(
437
            "The regular expression to match a log message and capture fields.")
438
        .with_min_length(1)
439
        .for_field(&external_log_format::pattern::p_pcre),
440
};
441

442
static constexpr json_path_handler_base::enum_value_t SUBSECOND_UNIT_ENUM[] = {
443
    {"milli"_frag, log_format::subsecond_unit::milli},
444
    {"micro"_frag, log_format::subsecond_unit::micro},
445
    {"nano"_frag, log_format::subsecond_unit::nano},
446

447
    json_path_handler_base::ENUM_TERMINATOR,
448
};
449

450
static constexpr json_path_handler_base::enum_value_t TS_POR_ENUM[] = {
451
    {"send"_frag, timestamp_point_of_reference_t::send},
452
    {"start"_frag, timestamp_point_of_reference_t::start},
453

454
    json_path_handler_base::ENUM_TERMINATOR,
455
};
456

457
static constexpr json_path_handler_base::enum_value_t ALIGN_ENUM[] = {
458
    {"left"_frag, external_log_format::json_format_element::align_t::LEFT},
459
    {"right"_frag, external_log_format::json_format_element::align_t::RIGHT},
460

461
    json_path_handler_base::ENUM_TERMINATOR,
462
};
463

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

474
    json_path_handler_base::ENUM_TERMINATOR,
475
};
476

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

486
    json_path_handler_base::ENUM_TERMINATOR,
487
};
488

489
static constexpr json_path_handler_base::enum_value_t OPID_SOURCE_ENUM[] = {
490
    {"from-description"_frag, log_format::opid_source_t::from_description},
491
    {"from-whole-msg"_frag, log_format::opid_source_t::from_whole_msg},
492

493
    json_path_handler_base::ENUM_TERMINATOR,
494
};
495

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

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

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

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

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

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

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

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

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

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

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

564
static constexpr json_path_handler_base::enum_value_t KIND_ENUM[] = {
565
    {"string"_frag, value_kind_t::VALUE_TEXT},
566
    {"integer"_frag, value_kind_t::VALUE_INTEGER},
567
    {"float"_frag, value_kind_t::VALUE_FLOAT},
568
    {"boolean"_frag, value_kind_t::VALUE_BOOLEAN},
569
    {"json"_frag, value_kind_t::VALUE_JSON},
570
    {"struct"_frag, value_kind_t::VALUE_STRUCT},
571
    {"quoted"_frag, value_kind_t::VALUE_QUOTED},
572
    {"xml"_frag, value_kind_t::VALUE_XML},
573
    {"timestamp"_frag, value_kind_t::VALUE_TIMESTAMP},
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 highlighter_def_handlers = {
613
    yajlpp::property_handler("pattern")
614
        .with_synopsis("<regex>")
615
        .with_description(
616
            "A regular expression to highlight in logs of this format.")
617
        .for_field(&external_log_format::highlighter_def::hd_pattern),
618

619
    yajlpp::property_handler("color")
620
        .with_synopsis("#<hex>|<name>")
621
        .with_description("The color to use when highlighting this pattern.")
622
        .for_field(&external_log_format::highlighter_def::hd_color),
623

624
    yajlpp::property_handler("background-color")
625
        .with_synopsis("#<hex>|<name>")
626
        .with_description(
627
            "The background color to use when highlighting this pattern.")
628
        .for_field(&external_log_format::highlighter_def::hd_background_color),
629

630
    yajlpp::property_handler("underline")
631
        .with_synopsis("<enabled>")
632
        .with_description("Highlight this pattern with an underline.")
633
        .for_field(&external_log_format::highlighter_def::hd_underline),
634

635
    yajlpp::property_handler("blink")
636
        .with_synopsis("<enabled>")
637
        .with_description("Highlight this pattern by blinking.")
638
        .for_field(&external_log_format::highlighter_def::hd_blink),
639
    yajlpp::property_handler("nestable")
640
        .with_synopsis("<enabled>")
641
        .with_description("This highlight can be nested in another highlight.")
642
        .for_field(&external_log_format::highlighter_def::hd_nestable),
643
};
644

645
static const struct json_path_container highlight_handlers = {
646
    yajlpp::pattern_property_handler(R"((?<highlight_name>[^/]+))")
647
        .with_description("The definition of a highlight")
648
        .with_obj_provider<external_log_format::highlighter_def,
649
                           external_log_format>(
650
            [](const yajlpp_provider_context& ypc, external_log_format* root) {
4,851✔
651
                auto* retval
652
                    = &(root->elf_highlighter_patterns[ypc.get_substr_i(0)]);
4,851✔
653

654
                return retval;
4,851✔
655
            })
656
        .with_children(highlighter_def_handlers),
657
};
658

659
static const struct json_path_container value_highlight_handlers = {
660
    yajlpp::pattern_property_handler(R"((?<highlight_name>[^/]+))")
661
        .with_description("The definition of a highlight")
662
        .with_obj_provider<external_log_format::highlighter_def,
663
                           external_log_format::value_def>(
664
            [](const yajlpp_provider_context& ypc,
7,474✔
665
               external_log_format::value_def* root) {
666
                auto* retval
667
                    = &(root->vd_highlighter_patterns[ypc.get_substr_i(0)]);
7,474✔
668

669
                return retval;
7,474✔
670
            })
671
        .with_children(highlighter_def_handlers),
672
};
673

674
static const struct json_path_container value_def_handlers = {
675
    yajlpp::property_handler("kind")
676
        .with_synopsis("<data-type>")
677
        .with_description("The type of data in the field")
678
        .with_enum_values(KIND_ENUM)
679
        .for_field(&external_log_format::value_def::vd_meta,
680
                   &logline_value_meta::lvm_kind),
681

682
    yajlpp::property_handler("collate")
683
        .with_synopsis("<function>")
684
        .with_description("The collating function to use for this column")
685
        .for_field(&external_log_format::value_def::vd_collate),
686

687
    yajlpp::property_handler("unit")
688
        .with_description("Unit definitions for this field")
689
        .with_children(unit_handlers),
690

691
    yajlpp::property_handler("identifier")
692
        .with_synopsis("<bool>")
693
        .with_description("Indicates whether or not this field contains an "
694
                          "identifier that should be highlighted")
695
        .for_field(&external_log_format::value_def::vd_meta,
696
                   &logline_value_meta::lvm_identifier),
697

698
    yajlpp::property_handler("foreign-key")
699
        .with_synopsis("<bool>")
700
        .with_description("Indicates whether or not this field should be "
701
                          "treated as a foreign key for row in another table")
702
        .for_field(&external_log_format::value_def::vd_meta,
703
                   &logline_value_meta::lvm_foreign_key),
704

705
    yajlpp::property_handler("hidden")
706
        .with_synopsis("<bool>")
707
        .with_description(
708
            "Indicates whether or not this field should be hidden")
709
        .for_field(&external_log_format::value_def::vd_meta,
710
                   &logline_value_meta::lvm_hidden),
711

712
    yajlpp::property_handler("action-list#")
713
        .with_synopsis("<string>")
714
        .with_description("Actions to execute when this field is clicked on")
715
        .for_field(&external_log_format::value_def::vd_action_list),
716

717
    yajlpp::property_handler("rewriter")
718
        .with_synopsis("<command>")
719
        .with_description(
720
            "A command that will rewrite this field when pretty-printing")
721
        .for_field(&external_log_format::value_def::vd_rewriter)
722
        .with_example(
723
            ";SELECT :sc_status || ' (' || (SELECT message FROM "
724
            "http_status_codes WHERE status = :sc_status) || ') '"_frag),
725

726
    yajlpp::property_handler("description")
727
        .with_synopsis("<string>")
728
        .with_description("A description of the field")
729
        .for_field(&external_log_format::value_def::vd_description),
730

731
    yajlpp::property_handler("highlights")
732
        .with_description("The set of highlight definitions")
733
        .with_children(value_highlight_handlers),
734
};
735

736
static const struct json_path_container sample_handlers = {
737
    yajlpp::property_handler("description")
738
        .with_synopsis("<text>")
739
        .with_description("A description of this sample.")
740
        .for_field(&external_log_format::sample_t::s_description),
741
    yajlpp::property_handler("line")
742
        .with_synopsis("<log-line>")
743
        .with_description(
744
            "A sample log line that should match a pattern in this format.")
745
        .for_field(&external_log_format::sample_t::s_line),
746

747
    yajlpp::property_handler("level")
748
        .with_enum_values(LEVEL_ENUM)
749
        .with_description("The expected level for this sample log line.")
750
        .for_field(&external_log_format::sample_t::s_level),
751
};
752

753
static constexpr json_path_handler_base::enum_value_t TYPE_ENUM[] = {
754
    {"text"_frag, external_log_format::elf_type_t::ELF_TYPE_TEXT},
755
    {"json"_frag, external_log_format::elf_type_t::ELF_TYPE_JSON},
756
    {"csv"_frag, external_log_format::elf_type_t::ELF_TYPE_CSV},
757

758
    json_path_handler_base::ENUM_TERMINATOR,
759
};
760

761
static const struct json_path_container regex_handlers = {
762
    yajlpp::pattern_property_handler(R"((?<pattern_name>[^/]+))")
763
        .with_description("The set of patterns used to match log messages")
764
        .with_obj_provider(pattern_provider)
765
        .with_children(pattern_handlers),
766
};
767

768
static const struct json_path_container level_handlers = {
769
    yajlpp::pattern_property_handler("(?<level>trace|debug[2345]?|info|stats|"
770
                                     "notice|warning|error|critical|fatal)")
771
        .add_cb(read_levels)
772
        .add_cb(read_level_int)
773
        .with_synopsis("<pattern|integer>")
774
        .with_description("The regular expression used to match the log text "
775
                          "for this level.  "
776
                          "For JSON logs with numeric levels, this should be "
777
                          "the number for the corresponding level."),
778
};
779

780
static const struct json_path_container value_handlers = {
781
    yajlpp::pattern_property_handler("(?<value_name>[^/]+)")
782
        .with_description(
783
            "The set of values captured by the log message patterns")
784
        .with_obj_provider(value_def_provider)
785
        .with_children(value_def_handlers),
786
};
787

788
static const struct json_path_container tag_path_handlers = {
789
    yajlpp::property_handler("glob")
790
        .with_synopsis("<glob>")
791
        .with_description("The glob to match against file paths")
792
        .with_example("*/system.log*"_frag)
793
        .for_field(&format_tag_def::path_restriction::p_glob),
794
};
795

796
static const struct json_path_container format_tag_def_handlers = {
797
    yajlpp::property_handler("paths#")
798
        .with_description("Restrict tagging to the given paths")
799
        .for_field(&format_tag_def::ftd_paths)
800
        .with_children(tag_path_handlers),
801
    yajlpp::property_handler("pattern")
802
        .with_synopsis("<regex>")
803
        .with_description("The regular expression to match against the body of "
804
                          "the log message")
805
        .with_example("\\w+ is down"_frag)
806
        .for_field(&format_tag_def::ftd_pattern),
807
    yajlpp::property_handler("description")
808
        .with_synopsis("<string>")
809
        .with_description("A description of this tag")
810
        .for_field(&format_tag_def::ftd_description),
811
    json_path_handler("level")
812
        .with_synopsis("<log-level>")
813
        .with_description("Constrain hits to log messages with this level")
814
        .with_enum_values(LEVEL_ENUM)
815
        .for_field(&format_tag_def::ftd_level),
816
};
817

818
static const struct json_path_container tag_handlers = {
819
    yajlpp::pattern_property_handler(R"((?<tag_name>[\w:;\._\-]+))")
820
        .with_description("The name of the tag to apply")
821
        .with_obj_provider(format_tag_def_provider)
822
        .with_children(format_tag_def_handlers),
823
};
824

825
static const struct json_path_container format_partition_def_handlers = {
826
    yajlpp::property_handler("paths#")
827
        .with_description("Restrict partitioning to the given paths")
828
        .for_field(&format_partition_def::fpd_paths)
829
        .with_children(tag_path_handlers),
830
    yajlpp::property_handler("pattern")
831
        .with_synopsis("<regex>")
832
        .with_description("The regular expression to match against the body of "
833
                          "the log message")
834
        .with_example("\\w+ is down"_frag)
835
        .for_field(&format_partition_def::fpd_pattern),
836
    yajlpp::property_handler("description")
837
        .with_synopsis("<string>")
838
        .with_description("A description of this partition")
839
        .for_field(&format_partition_def::fpd_description),
840
    json_path_handler("level")
841
        .with_synopsis("<log-level>")
842
        .with_description("Constrain hits to log messages with this level")
843
        .with_enum_values(LEVEL_ENUM)
844
        .for_field(&format_partition_def::fpd_level),
845
};
846

847
static const struct json_path_container partition_handlers = {
848
    yajlpp::pattern_property_handler(R"((?<partition_type>[\w:;\._\- ]+))")
849
        .with_description("The type of partition to apply")
850
        .with_obj_provider(format_partition_def_provider)
851
        .with_children(format_partition_def_handlers),
852
};
853

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

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

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

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

893
                return retval;
39,303✔
894
            })
895
        .with_children(search_table_def_handlers),
896
};
897

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

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

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

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

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

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

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

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

991
const struct json_path_container format_handlers = {
992
    yajlpp::property_handler("regex")
993
        .with_description(
994
            "The set of regular expressions used to match log messages")
995
        .with_children(regex_handlers),
996

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

1109
    yajlpp::property_handler("value")
1110
        .with_description("The set of value definitions")
1111
        .with_children(value_handlers),
1112

1113
    yajlpp::property_handler("tags")
1114
        .with_description("The tags to automatically apply to log messages")
1115
        .with_children(tag_handlers),
1116

1117
    yajlpp::property_handler("partitions")
1118
        .with_description(
1119
            "The partitions to automatically apply to log messages")
1120
        .with_children(partition_handlers),
1121

1122
    yajlpp::property_handler("action").with_children(action_handlers),
1123
    yajlpp::property_handler("sample#")
1124
        .with_description("An array of sample log messages to be tested "
1125
                          "against the log message patterns")
1126
        .with_obj_provider(sample_provider)
1127
        .with_children(sample_handlers),
1128

1129
    yajlpp::property_handler("line-format#")
1130
        .with_description("The display format for JSON-encoded log messages")
1131
        .with_obj_provider(line_format_provider)
1132
        .add_cb(read_json_constant)
1133
        .with_children(line_format_handlers),
1134
    json_path_handler("search-table")
1135
        .with_description(
1136
            "Search tables to automatically define for this log format")
1137
        .with_children(search_table_handlers),
1138

1139
    yajlpp::property_handler("highlights")
1140
        .with_description("The set of highlight definitions")
1141
        .with_children(highlight_handlers),
1142

1143
    yajlpp::property_handler("file-type")
1144
        .with_synopsis("text|json|csv")
1145
        .with_description("The type of file that contains the log messages")
1146
        .with_enum_values(TYPE_ENUM)
1147
        .for_field(&external_log_format::elf_type),
1148

1149
    yajlpp::property_handler("max-unrecognized-lines")
1150
        .with_synopsis("<lines>")
1151
        .with_description("The maximum number of lines in a file to use when "
1152
                          "detecting the format")
1153
        .with_min_value(1)
1154
        .for_field(&log_format::lf_max_unrecognized_lines),
1155
};
1156

1157
static int
1158
read_id(yajlpp_parse_context* ypc,
57,019✔
1159
        const unsigned char* str,
1160
        size_t len,
1161
        yajl_string_props_t*)
1162
{
1163
    auto* ud = static_cast<loader_userdata*>(ypc->ypc_userdata);
57,019✔
1164
    auto file_id = std::string((const char*) str, len);
57,019✔
1165

1166
    ud->ud_file_schema = file_id;
57,019✔
1167
    if (SUPPORTED_FORMAT_SCHEMAS.find(file_id)
57,019✔
1168
        == SUPPORTED_FORMAT_SCHEMAS.end())
114,038✔
1169
    {
1170
        const auto* handler = ypc->ypc_current_handler;
1✔
1171
        attr_line_t notes{"expecting one of the following $schema values:"};
1✔
1172

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

1187
    return 1;
57,019✔
1188
}
57,019✔
1189

1190
const struct json_path_container root_format_handler = json_path_container{
1191
    json_path_handler("$schema", read_id)
1192
        .with_synopsis("The URI of the schema for this file")
1193
        .with_description("Specifies the type of this file"),
1194

1195
    yajlpp::pattern_property_handler("(?<format_name>\\w+)")
1196
        .with_description("The definition of a log file format.")
1197
        .with_obj_provider(ensure_format)
1198
        .with_children(format_handlers),
1199
}
1200
    .with_schema_id(DEFAULT_FORMAT_SCHEMA);
1201

1202
static void
1203
write_sample_file()
786✔
1204
{
1205
    const auto dstdir = lnav::paths::dotlnav();
786✔
1206
    for (const auto& bsf : lnav_format_json) {
56,592✔
1207
        auto sample_path = dstdir
1208
            / fmt::format(FMT_STRING("formats/default/{}.sample"),
223,224✔
1209
                          bsf.get_name());
111,612✔
1210

1211
        const auto& name_sf = bsf.get_name();
55,806✔
1212
        auto stat_res = lnav::filesystem::stat_file(sample_path);
55,806✔
1213
        if (stat_res.isOk()) {
55,806✔
1214
            auto st = stat_res.unwrap();
48,138✔
1215
            if (st.st_mtime >= lnav::filesystem::self_mtime()) {
48,138✔
1216
                log_debug("skipping writing sample: %.*s (mtimes %ld >= %lld)",
48,138✔
1217
                          name_sf.length(),
1218
                          name_sf.data(),
1219
                          st.st_mtime,
1220
                          lnav::filesystem::self_mtime());
1221
                continue;
48,138✔
1222
            }
1223
            log_debug("sample file needs to be updated: %.*s",
×
1224
                      name_sf.length(),
1225
                      name_sf.data());
1226
        } else {
1227
            log_debug("sample file does not exist: %.*s",
7,668✔
1228
                      name_sf.length(),
1229
                      name_sf.data());
1230
        }
1231

1232
        auto sfp = bsf.to_string_fragment_producer();
7,668✔
1233
        auto write_res = lnav::filesystem::write_file(
1234
            sample_path,
1235
            *sfp,
7,668✔
1236
            {lnav::filesystem::write_file_options::read_only});
15,336✔
1237

1238
        if (write_res.isErr()) {
7,668✔
1239
            auto msg = write_res.unwrapErr();
4,828✔
1240
            fprintf(stderr,
4,828✔
1241
                    "error:unable to write default format file: %s -- %s\n",
1242
                    sample_path.c_str(),
1243
                    msg.c_str());
1244
        }
4,828✔
1245
    }
103,944✔
1246

1247
    for (const auto& bsf : lnav_sh_scripts) {
4,716✔
1248
        auto sh_path = dstdir
1249
            / fmt::format(FMT_STRING("formats/default/{}"), bsf.get_name());
15,720✔
1250
        auto stat_res = lnav::filesystem::stat_file(sh_path);
3,930✔
1251
        if (stat_res.isOk()) {
3,930✔
1252
            auto st = stat_res.unwrap();
3,390✔
1253
            if (st.st_mtime >= lnav::filesystem::self_mtime()) {
3,390✔
1254
                continue;
3,390✔
1255
            }
1256
        }
1257

1258
        auto sfp = bsf.to_string_fragment_producer();
540✔
1259
        auto write_res = lnav::filesystem::write_file(
1260
            sh_path,
1261
            *sfp,
540✔
1262
            {
1263
                lnav::filesystem::write_file_options::executable,
1264
                lnav::filesystem::write_file_options::read_only,
1265
            });
1,080✔
1266

1267
        if (write_res.isErr()) {
540✔
1268
            auto msg = write_res.unwrapErr();
340✔
1269
            fprintf(stderr,
340✔
1270
                    "error:unable to write default text file: %s -- %s\n",
1271
                    sh_path.c_str(),
1272
                    msg.c_str());
1273
        }
340✔
1274
    }
7,320✔
1275

1276
    for (const auto& bsf : lnav_scripts) {
14,934✔
1277
        script_metadata meta;
14,148✔
1278
        auto sf = bsf.to_string_fragment_producer()->to_string();
14,148✔
1279

1280
        meta.sm_name = bsf.get_name().to_string();
14,148✔
1281
        extract_metadata(sf, meta);
14,148✔
1282
        auto path
1283
            = fmt::format(FMT_STRING("formats/default/{}.lnav"), meta.sm_name);
42,444✔
1284
        auto script_path = dstdir / path;
14,148✔
1285
        auto stat_res = lnav::filesystem::stat_file(script_path);
14,148✔
1286
        if (stat_res.isOk()) {
14,148✔
1287
            auto st = stat_res.unwrap();
12,244✔
1288
            if (st.st_mtime >= lnav::filesystem::self_mtime()) {
12,244✔
1289
                continue;
12,244✔
1290
            }
1291
        }
1292

1293
        auto write_res = lnav::filesystem::write_file(
1294
            script_path,
1295
            sf,
1296
            {
1297
                lnav::filesystem::write_file_options::executable,
1298
                lnav::filesystem::write_file_options::read_only,
1299
            });
1,904✔
1300
        if (write_res.isErr()) {
1,904✔
1301
            fprintf(stderr,
1,224✔
1302
                    "error:unable to write default script file: %s -- %s\n",
1303
                    script_path.c_str(),
1304
                    strerror(errno));
1,224✔
1305
        }
1306
    }
63,124✔
1307
}
786✔
1308

1309
static void
1310
format_error_reporter(const yajlpp_parse_context& ypc,
16✔
1311
                      const lnav::console::user_message& msg)
1312
{
1313
    auto* ud = (loader_userdata*) ypc.ypc_userdata;
16✔
1314

1315
    ud->ud_errors->emplace_back(msg);
16✔
1316
}
16✔
1317

1318
std::vector<intern_string_t>
1319
load_format_file(const std::filesystem::path& filename,
1,215✔
1320
                 std::vector<lnav::console::user_message>& errors)
1321
{
1322
    std::vector<intern_string_t> retval;
1,215✔
1323
    loader_userdata ud;
1,215✔
1324
    auto_fd fd;
1,215✔
1325

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

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

1379
        if (ud.ud_file_schema.empty()) {
1,215✔
1380
            static const auto SCHEMA_LINE
1381
                = attr_line_t()
2✔
1382
                      .append(
4✔
1383
                          fmt::format(FMT_STRING("    \"$schema\": \"{}\","),
8✔
1384
                                      *SUPPORTED_FORMAT_SCHEMAS.begin()))
4✔
1385
                      .with_attr_for_all(VC_ROLE.value(role_t::VCR_QUOTED_CODE))
4✔
1386
                      .move();
4✔
1387

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

1404
    return retval;
2,430✔
1405
}
1,215✔
1406

1407
static void
1408
load_from_path(const std::filesystem::path& path,
2,115✔
1409
               std::vector<lnav::console::user_message>& errors)
1410
{
1411
    auto format_path = path / "formats/*/*.json";
2,115✔
1412
    static_root_mem<glob_t, globfree> gl;
2,115✔
1413

1414
    log_info("loading formats from path: %s", format_path.c_str());
2,115✔
1415
    if (glob(format_path.c_str(), 0, nullptr, gl.inout()) == 0) {
2,115✔
1416
        for (int lpc = 0; lpc < (int) gl->gl_pathc; lpc++) {
1,314✔
1417
            auto filepath = std::filesystem::path(gl->gl_pathv[lpc]);
1,212✔
1418

1419
            if (startswith(filepath.filename().string(), "config.")) {
1,212✔
1420
                log_info("  not loading config as format: %s",
×
1421
                         filepath.c_str());
1422
                continue;
×
1423
            }
1424

1425
            auto format_list = load_format_file(filepath, errors);
1,212✔
1426
            if (format_list.empty()) {
1,212✔
1427
                log_warning("Empty format file: %s", filepath.c_str());
2✔
1428
            } else {
1429
                log_info("contents of format file '%s':", filepath.c_str());
1,210✔
1430
                for (auto iter = format_list.begin(); iter != format_list.end();
2,520✔
1431
                     ++iter)
1,310✔
1432
                {
1433
                    log_info("  found format: %s", iter->get());
1,310✔
1434
                }
1435
            }
1436
        }
1,212✔
1437
    }
1438
}
2,115✔
1439

1440
void
1441
load_formats(const std::vector<std::filesystem::path>& extra_paths,
786✔
1442
             std::vector<lnav::console::user_message>& errors)
1443
{
1444
    auto op_guard = lnav_opid_guard::once(__FUNCTION__);
786✔
1445

1446
    auto default_source = lnav::paths::dotlnav() / "default";
786✔
1447
    std::vector<intern_string_t> retval;
786✔
1448
    loader_userdata ud;
786✔
1449
    yajl_handle handle;
1450

1451
    write_sample_file();
786✔
1452

1453
    log_debug("Loading default formats");
786✔
1454
    for (const auto& bsf : lnav_format_json) {
56,592✔
1455
        yajlpp_parse_context ypc_builtin(intern_string::lookup(bsf.get_name()),
55,806✔
1456
                                         &root_format_handler);
55,806✔
1457
        handle = yajl_alloc(&ypc_builtin.ypc_callbacks, nullptr, &ypc_builtin);
55,806✔
1458
        ud.ud_parse_context = &ypc_builtin;
55,806✔
1459
        ud.ud_format_names = &retval;
55,806✔
1460
        ud.ud_errors = &errors;
55,806✔
1461
        ypc_builtin.with_obj(ud)
111,612✔
1462
            .with_handle(handle)
55,806✔
1463
            .with_error_reporter(format_error_reporter)
55,806✔
1464
            .ypc_userdata = &ud;
111,612✔
1465
        yajl_config(handle, yajl_allow_comments, 1);
55,806✔
1466
        auto sf = bsf.to_string_fragment_producer();
55,806✔
1467
        ypc_builtin.parse(*sf);
55,806✔
1468
        yajl_free(handle);
55,806✔
1469
    }
55,806✔
1470

1471
    for (const auto& extra_path : extra_paths) {
2,901✔
1472
        load_from_path(extra_path, errors);
2,115✔
1473
    }
1474

1475
    std::vector<std::shared_ptr<external_log_format>> alpha_ordered_formats;
786✔
1476
    for (auto iter = LOG_FORMATS.begin(); iter != LOG_FORMATS.end(); ++iter) {
57,802✔
1477
        auto& elf = iter->second;
57,016✔
1478
        elf->build(errors);
57,016✔
1479

1480
        for (auto& check_iter : LOG_FORMATS) {
4,205,530✔
1481
            if (iter->first == check_iter.first) {
4,148,514✔
1482
                continue;
57,016✔
1483
            }
1484

1485
            auto& check_elf = check_iter.second;
4,091,498✔
1486
            if (elf->match_samples(check_elf->elf_samples)) {
4,091,498✔
1487
                log_warning(
16,454✔
1488
                    "Format collision, format '%s' matches sample from '%s'",
1489
                    elf->get_name().get(),
1490
                    check_elf->get_name().get());
1491
                elf->elf_collision.push_back(check_elf->get_name());
16,454✔
1492
            }
1493
        }
1494

1495
        alpha_ordered_formats.push_back(elf);
57,016✔
1496
    }
1497

1498
    auto& graph_ordered_formats = external_log_format::GRAPH_ORDERED_FORMATS;
786✔
1499

1500
    while (!alpha_ordered_formats.empty()) {
10,039✔
1501
        std::vector<intern_string_t> popped_formats;
9,253✔
1502

1503
        for (auto iter = alpha_ordered_formats.begin();
9,253✔
1504
             iter != alpha_ordered_formats.end();)
105,237✔
1505
        {
1506
            auto elf = *iter;
95,984✔
1507
            if (elf->elf_collision.empty()) {
95,984✔
1508
                iter = alpha_ordered_formats.erase(iter);
57,016✔
1509
                popped_formats.push_back(elf->get_name());
57,016✔
1510
                graph_ordered_formats.push_back(elf);
57,016✔
1511
            } else {
1512
                ++iter;
38,968✔
1513
            }
1514
        }
95,984✔
1515

1516
        if (popped_formats.empty() && !alpha_ordered_formats.empty()) {
9,253✔
1517
            bool broke_cycle = false;
2,661✔
1518

1519
            log_warning("Detected a cycle...");
2,661✔
1520
            for (const auto& elf : alpha_ordered_formats) {
15,555✔
1521
                if (elf->elf_builtin_format) {
13,197✔
1522
                    log_warning("  Skipping builtin format -- %s",
12,894✔
1523
                                elf->get_name().get());
1524
                } else {
1525
                    log_warning("  Breaking cycle by picking -- %s",
303✔
1526
                                elf->get_name().get());
1527
                    elf->elf_collision.clear();
303✔
1528
                    broke_cycle = true;
303✔
1529
                    break;
303✔
1530
                }
1531
            }
1532
            if (!broke_cycle) {
2,661✔
1533
                alpha_ordered_formats.front()->elf_collision.clear();
2,358✔
1534
            }
1535
        }
1536

1537
        for (const auto& elf : alpha_ordered_formats) {
48,221✔
1538
            for (auto& popped_format : popped_formats) {
438,616✔
1539
                elf->elf_collision.remove(popped_format);
399,648✔
1540
            }
1541
        }
1542
    }
9,253✔
1543

1544
    log_info("Format order:") for (auto& graph_ordered_format :
1,572✔
1545
                                   graph_ordered_formats)
58,588✔
1546
    {
1547
        log_info("  %s", graph_ordered_format->get_name().get());
57,016✔
1548
    }
1549

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

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

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

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

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

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

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

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

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

1647
    if (!meta_out.sm_synopsis.empty()) {
14,892✔
1648
        size_t space = meta_out.sm_synopsis.find(' ');
14,853✔
1649

1650
        if (space == std::string::npos) {
14,853✔
1651
            space = meta_out.sm_synopsis.size();
10,759✔
1652
        }
1653
        meta_out.sm_name = meta_out.sm_synopsis.substr(0, space);
14,853✔
1654
    }
1655
}
14,892✔
1656

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

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

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

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

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

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

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

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

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

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

1734
    for (auto& root_format : root_formats) {
48,958✔
1735
        root_format.second->register_vtabs(vtab_manager, errors);
48,287✔
1736
    }
1737
}
671✔
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