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

tstack / lnav / 25348852825-3024

04 May 2026 11:18PM UTC coverage: 69.963% (+0.7%) from 69.226%
25348852825-3024

push

github

tstack
[ui] horizontal scroll should work on columns

Related to #1685

7 of 141 new or added lines in 5 files covered. (4.96%)

7760 existing lines in 84 files now uncovered.

57014 of 81492 relevant lines covered (69.96%)

622491.44 hits per line

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

93.35
/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)
5,360,766✔
84
{
85
    const intern_string_t name = ypc.get_substr_i(0);
5,360,766✔
86
    auto* formats = ud->ud_format_names;
5,360,766✔
87
    auto* retval = LOG_FORMATS[name].get();
5,360,766✔
88
    if (retval == nullptr) {
5,360,766✔
89
        LOG_FORMATS[name] = std::make_shared<external_log_format>(name);
67,117✔
90
        retval = LOG_FORMATS[name].get();
67,117✔
91
        log_debug("Loading format -- %s", name.get());
67,117✔
92
    }
93
    retval->elf_source_path.insert(ud->ud_format_path.parent_path().string());
5,360,766✔
94

95
    if (find(formats->begin(), formats->end(), name) == formats->end()) {
5,360,766✔
96
        formats->push_back(name);
67,225✔
97
    }
98

99
    if (!ud->ud_format_path.empty()) {
5,360,766✔
100
        const intern_string_t i_src_path
101
            = intern_string::lookup(ud->ud_format_path.string());
62,390✔
102
        auto srcs_iter = retval->elf_format_sources.find(i_src_path);
62,390✔
103
        if (srcs_iter == retval->elf_format_sources.end()) {
62,390✔
104
            retval->elf_format_source_order.emplace_back(ud->ud_format_path);
1,633✔
105
            retval->elf_format_sources[i_src_path]
3,266✔
106
                = ud->ud_parse_context->get_line_number();
1,633✔
107
        }
108
    }
109

110
    if (ud->ud_format_path.empty()) {
5,360,766✔
111
        retval->elf_builtin_format = true;
5,298,376✔
112
    }
113

114
    return retval;
5,360,766✔
115
}
116

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

123
    if (pat.get() == nullptr) {
368,526✔
124
        pat = std::make_shared<external_log_format::pattern>();
122,842✔
125
    }
126

127
    if (pat->p_config_path.empty()) {
368,526✔
128
        pat->p_name = intern_string::lookup(regex_name);
122,842✔
129
        pat->p_config_path = fmt::format(
245,684✔
130
            FMT_STRING("/{}/regex/{}"), elf->get_name(), regex_name);
614,210✔
131
    }
132

133
    return pat.get();
737,052✔
134
}
368,526✔
135

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

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

144
    if (iter == elf->elf_value_defs.end()) {
2,439,263✔
145
        retval = std::make_shared<external_log_format::value_def>(
583,693✔
146
            value_name,
147
            value_kind_t::VALUE_TEXT,
583,693✔
148
            logline_value_meta::external_column{},
×
149
            elf);
583,693✔
150
        elf->elf_value_defs[value_name] = retval;
583,693✔
151
        elf->elf_value_def_order.emplace_back(retval);
583,693✔
152
    } else {
153
        retval = iter->second;
1,855,570✔
154
    }
155

156
    return retval.get();
4,878,526✔
157
}
2,439,263✔
158

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

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

168
    if (iter == elf->lf_tag_defs.end()) {
15,822✔
169
        auto tag_with_hash = fmt::format(FMT_STRING("#{}"), tag_name);
8,532✔
170
        retval = std::make_shared<format_tag_def>(tag_with_hash);
2,844✔
171
        elf->lf_tag_defs[tag_name] = retval;
2,844✔
172
    } else {
2,844✔
173
        retval = iter->second;
12,978✔
174
    }
175

176
    return retval.get();
31,644✔
177
}
15,822✔
178

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

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

188
    if (iter == elf->lf_partition_defs.end()) {
15,008✔
189
        retval = std::make_shared<format_partition_def>(
5,682✔
190
            partition_name.to_string());
8,523✔
191
        elf->lf_partition_defs[partition_name] = retval;
2,841✔
192
    } else {
193
        retval = iter->second;
12,167✔
194
    }
195

196
    return retval.get();
30,016✔
197
}
15,008✔
198

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

UNCOV
206
    return &retval;
×
207
}
208

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

214
    return elf->jlf_line_format[index];
350,236✔
215
}
216

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

223
    jfe.jfe_type = external_log_format::json_log_field::VARIABLE;
300,000✔
224

225
    return &jfe;
300,000✔
226
}
227

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

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

238
    return 1;
4,297✔
239
}
4,297✔
240

241
static int
242
read_format_field(yajlpp_parse_context* ypc,
125,637✔
243
                  const unsigned char* str,
244
                  size_t len,
245
                  yajl_string_props_t*)
246
{
247
    auto elf = (external_log_format*) ypc->ypc_obj_stack.top();
125,637✔
248
    auto leading_slash = len > 0 && str[0] == '/';
125,637✔
249
    auto value = std::string((const char*) (leading_slash ? str + 1 : str),
250
                             leading_slash ? len - 1 : len);
125,637✔
251
    auto field_name = ypc->get_path_fragment(1);
125,637✔
252

253
    if (field_name == "timestamp-format") {
125,637✔
254
        elf->lf_timestamp_format.push_back(intern_string::lookup(value)->get());
13,885✔
255
    }
256

257
    return 1;
125,637✔
258
}
125,637✔
259

260
static int
261
read_levels(yajlpp_parse_context* ypc,
120,546✔
262
            const unsigned char* str,
263
            size_t len,
264
            yajl_string_props_t*)
265
{
266
    auto elf = (external_log_format*) ypc->ypc_obj_stack.top();
120,546✔
267
    auto regex = std::string((const char*) str, len);
120,546✔
268
    auto level_name_or_number = ypc->get_path_fragment(2);
120,546✔
269
    log_level_t level = string2level(level_name_or_number.c_str());
120,546✔
270
    auto value_frag = string_fragment::from_bytes(str, len);
120,546✔
271

272
    elf->elf_level_patterns[level].lp_pcre.pp_path = ypc->get_full_path();
120,546✔
273
    auto compile_res = lnav::pcre2pp::code::from(value_frag);
120,546✔
274
    if (compile_res.isErr()) {
120,546✔
275
        static const intern_string_t PATTERN_SRC
276
            = intern_string::lookup("pattern");
3✔
277
        auto ce = compile_res.unwrapErr();
1✔
278
        ypc->ypc_current_handler->report_error(
1✔
279
            ypc,
280
            value_frag.to_string(),
2✔
281
            lnav::console::to_user_message(PATTERN_SRC, ce));
2✔
282
    } else {
1✔
283
        elf->elf_level_patterns[level].lp_pcre.pp_value
120,545✔
284
            = compile_res.unwrap().to_shared();
241,090✔
285
    }
286

287
    return 1;
120,546✔
288
}
120,546✔
289

290
static int
291
read_level_int(yajlpp_parse_context* ypc, long long val)
15,703✔
292
{
293
    auto elf = (external_log_format*) ypc->ypc_obj_stack.top();
15,703✔
294
    auto level_name_or_number = ypc->get_path_fragment(2);
15,703✔
295
    log_level_t level = string2level(level_name_or_number.c_str());
15,703✔
296

297
    elf->elf_level_pairs.emplace_back(val, level);
15,703✔
298

299
    return 1;
15,703✔
300
}
15,703✔
301

302
static int
303
read_action_def(yajlpp_parse_context* ypc,
911✔
304
                const unsigned char* str,
305
                size_t len,
306
                yajl_string_props_t*)
307
{
308
    auto elf = (external_log_format*) ypc->ypc_obj_stack.top();
911✔
309
    auto action_name = ypc->get_path_fragment(2);
911✔
310
    auto field_name = ypc->get_path_fragment(3);
911✔
311
    auto val = std::string((const char*) str, len);
911✔
312

313
    elf->lf_action_defs[action_name].ad_name = action_name;
911✔
314
    if (field_name == "label") {
911✔
315
        elf->lf_action_defs[action_name].ad_label = val;
911✔
316
    }
317

318
    return 1;
911✔
319
}
911✔
320

321
static int
322
read_action_bool(yajlpp_parse_context* ypc, int val)
911✔
323
{
324
    auto elf = (external_log_format*) ypc->ypc_obj_stack.top();
911✔
325
    auto action_name = ypc->get_path_fragment(2);
911✔
326
    auto field_name = ypc->get_path_fragment(3);
911✔
327

328
    elf->lf_action_defs[action_name].ad_capture_output = val;
911✔
329

330
    return 1;
911✔
331
}
911✔
332

333
static int
334
read_action_cmd(yajlpp_parse_context* ypc,
911✔
335
                const unsigned char* str,
336
                size_t len,
337
                yajl_string_props_t*)
338
{
339
    auto elf = (external_log_format*) ypc->ypc_obj_stack.top();
911✔
340
    auto action_name = ypc->get_path_fragment(2);
911✔
341
    auto field_name = ypc->get_path_fragment(3);
911✔
342
    auto val = std::string((const char*) str, len);
911✔
343

344
    elf->lf_action_defs[action_name].ad_name = action_name;
911✔
345
    elf->lf_action_defs[action_name].ad_cmdline.push_back(val);
911✔
346

347
    return 1;
911✔
348
}
911✔
349

350
static external_log_format::sample_t&
351
ensure_sample(external_log_format* elf, int index)
271,875✔
352
{
353
    elf->elf_samples.resize(index + 1);
271,875✔
354

355
    return elf->elf_samples[index];
271,875✔
356
}
357

358
static external_log_format::sample_t*
359
sample_provider(const yajlpp_provider_context& ypc, external_log_format* elf)
271,875✔
360
{
361
    auto& sample = ensure_sample(elf, ypc.ypc_index);
271,875✔
362

363
    return &sample;
271,875✔
364
}
365

366
static int
367
read_json_constant(yajlpp_parse_context* ypc,
50,236✔
368
                   const unsigned char* str,
369
                   size_t len,
370
                   yajl_string_props_t*)
371
{
372
    auto val = std::string((const char*) str, len);
50,236✔
373
    auto elf = (external_log_format*) ypc->ypc_obj_stack.top();
50,236✔
374

375
    ypc->ypc_array_index.back() += 1;
50,236✔
376
    auto& jfe = ensure_json_format_element(elf, ypc->ypc_array_index.back());
50,236✔
377
    jfe.jfe_type = external_log_format::json_log_field::CONSTANT;
50,236✔
378
    jfe.jfe_default_value = val;
50,236✔
379

380
    return 1;
50,236✔
381
}
50,236✔
382

383
static const struct json_path_container pattern_handlers = {
384
    yajlpp::property_handler("pattern")
385
        .with_synopsis("<message-regex>")
386
        .with_description(
387
            "The regular expression to match a log message and capture fields.")
388
        .with_min_length(1)
389
        .for_field(&external_log_format::pattern::p_pcre),
390
};
391

392
static constexpr json_path_handler_base::enum_value_t SUBSECOND_UNIT_ENUM[] = {
393
    {"milli"_frag, log_format::subsecond_unit::milli},
394
    {"micro"_frag, log_format::subsecond_unit::micro},
395
    {"nano"_frag, log_format::subsecond_unit::nano},
396

397
    json_path_handler_base::ENUM_TERMINATOR,
398
};
399

400
static constexpr json_path_handler_base::enum_value_t TS_POR_ENUM[] = {
401
    {"end"_frag, timestamp_point_of_reference_t::end},
402
    {"start"_frag, timestamp_point_of_reference_t::start},
403

404
    json_path_handler_base::ENUM_TERMINATOR,
405
};
406

407
static constexpr json_path_handler_base::enum_value_t ALIGN_ENUM[] = {
408
    {"left"_frag, external_log_format::json_format_element::align_t::LEFT},
409
    {"right"_frag, external_log_format::json_format_element::align_t::RIGHT},
410

411
    json_path_handler_base::ENUM_TERMINATOR,
412
};
413

414
static constexpr json_path_handler_base::enum_value_t OVERFLOW_ENUM[] = {
415
    {"abbrev"_frag,
416
     external_log_format::json_format_element::overflow_t::ABBREV},
417
    {"truncate"_frag,
418
     external_log_format::json_format_element::overflow_t::TRUNCATE},
419
    {"dot-dot"_frag,
420
     external_log_format::json_format_element::overflow_t::DOTDOT},
421
    {"last-word"_frag,
422
     external_log_format::json_format_element::overflow_t::LASTWORD},
423

424
    json_path_handler_base::ENUM_TERMINATOR,
425
};
426

427
static constexpr json_path_handler_base::enum_value_t TRANSFORM_ENUM[] = {
428
    {"none"_frag, external_log_format::json_format_element::transform_t::NONE},
429
    {"uppercase"_frag,
430
     external_log_format::json_format_element::transform_t::UPPERCASE},
431
    {"lowercase"_frag,
432
     external_log_format::json_format_element::transform_t::LOWERCASE},
433
    {"capitalize"_frag,
434
     external_log_format::json_format_element::transform_t::CAPITALIZE},
435

436
    json_path_handler_base::ENUM_TERMINATOR,
437
};
438

439
static constexpr json_path_handler_base::enum_value_t OPID_SOURCE_ENUM[] = {
440
    {"from-description"_frag, log_format::opid_source_t::from_description},
441
    {"from-whole-msg"_frag, log_format::opid_source_t::from_whole_msg},
442

443
    json_path_handler_base::ENUM_TERMINATOR,
444
};
445

446
static const json_path_container line_format_handlers = {
447
    yajlpp::property_handler("field")
448
        .with_synopsis("<field-name>")
449
        .with_description(
450
            "The name of the field to substitute at this position")
451
        .for_field(&external_log_format::json_format_element::jfe_value),
452

453
    yajlpp::property_handler("default-value")
454
        .with_synopsis("<string>")
455
        .with_description(
456
            "The default value for this position if the field is null")
457
        .for_field(
458
            &external_log_format::json_format_element::jfe_default_value),
459

460
    yajlpp::property_handler("timestamp-format")
461
        .with_synopsis("<string>")
462
        .with_min_length(1)
463
        .with_description("The strftime(3) format for this field")
464
        .for_field(&external_log_format::json_format_element::jfe_ts_format),
465

466
    yajlpp::property_handler("min-width")
467
        .with_min_value(0)
468
        .with_synopsis("<size>")
469
        .with_description("The minimum width of the field")
470
        .for_field(&external_log_format::json_format_element::jfe_min_width),
471

472
    yajlpp::property_handler("auto-width")
473
        .with_description("Automatically detect the necessary width of the "
474
                          "field based on the observed values")
475
        .for_field(&external_log_format::json_format_element::jfe_auto_width),
476

477
    yajlpp::property_handler("max-width")
478
        .with_min_value(0)
479
        .with_synopsis("<size>")
480
        .with_description("The maximum width of the field")
481
        .for_field(&external_log_format::json_format_element::jfe_max_width),
482

483
    yajlpp::property_handler("align")
484
        .with_synopsis("left|right")
485
        .with_description(
486
            "Align the text in the column to the left or right side")
487
        .with_enum_values(ALIGN_ENUM)
488
        .for_field(&external_log_format::json_format_element::jfe_align),
489

490
    yajlpp::property_handler("overflow")
491
        .with_synopsis("abbrev|truncate|dot-dot")
492
        .with_description("Overflow style")
493
        .with_enum_values(OVERFLOW_ENUM)
494
        .for_field(&external_log_format::json_format_element::jfe_overflow),
495

496
    yajlpp::property_handler("text-transform")
497
        .with_synopsis("none|uppercase|lowercase|capitalize")
498
        .with_description("Text transformation")
499
        .with_enum_values(TRANSFORM_ENUM)
500
        .for_field(
501
            &external_log_format::json_format_element::jfe_text_transform),
502

503
    yajlpp::property_handler("prefix")
504
        .with_synopsis("<str>")
505
        .with_description("Text to prepend to the value")
506
        .for_field(&external_log_format::json_format_element::jfe_prefix),
507

508
    yajlpp::property_handler("suffix")
509
        .with_synopsis("<str>")
510
        .with_description("Text to append to the value")
511
        .for_field(&external_log_format::json_format_element::jfe_suffix),
512
};
513

514
static constexpr json_path_handler_base::enum_value_t KIND_ENUM[] = {
515
    {"string"_frag, value_kind_t::VALUE_TEXT},
516
    {"integer"_frag, value_kind_t::VALUE_INTEGER},
517
    {"float"_frag, value_kind_t::VALUE_FLOAT},
518
    {"boolean"_frag, value_kind_t::VALUE_BOOLEAN},
519
    {"json"_frag, value_kind_t::VALUE_JSON},
520
    {"struct"_frag, value_kind_t::VALUE_STRUCT},
521
    {"quoted"_frag, value_kind_t::VALUE_QUOTED},
522
    {"xml"_frag, value_kind_t::VALUE_XML},
523
    {"timestamp"_frag, value_kind_t::VALUE_TIMESTAMP},
524
    {"any"_frag, value_kind_t::VALUE_ANY},
525

526
    json_path_handler_base::ENUM_TERMINATOR,
527
};
528

529
static constexpr json_path_handler_base::enum_value_t SCALE_OP_ENUM[] = {
530
    {"identity"_frag, scale_op_t::SO_IDENTITY},
531
    {"multiply"_frag, scale_op_t::SO_MULTIPLY},
532
    {"divide"_frag, scale_op_t::SO_DIVIDE},
533

534
    json_path_handler_base::ENUM_TERMINATOR,
535
};
536

537
static const struct json_path_container scaling_factor_handlers = {
538
    yajlpp::property_handler("op")
539
        .with_enum_values(SCALE_OP_ENUM)
540
        .for_field(&scaling_factor::sf_op),
541

542
    yajlpp::property_handler("value").for_field(&scaling_factor::sf_value),
543
};
544

545
static const struct json_path_container scale_handlers = {
546
    yajlpp::pattern_property_handler("(?<scale>[^/]+)")
547
        .with_obj_provider(scaling_factor_provider)
548
        .with_children(scaling_factor_handlers),
549
};
550

551
static const struct json_path_container unit_handlers = {
552
    yajlpp::property_handler("field")
553
        .with_synopsis("<field-name>")
554
        .with_description(
555
            "The name of the field that contains the units for this field")
556
        .for_field(&external_log_format::value_def::vd_unit_field),
557

558
    yajlpp::property_handler("scaling-factor")
559
        .with_description("Transforms the numeric value by the given factor")
560
        .with_children(scale_handlers),
561

562
    yajlpp::property_handler("suffix")
563
        .with_synopsis("<suffix>")
564
        .with_description(
565
            "The display suffix for this field; lnav uses the suffix to "
566
            "pick a humanization family (e.g. \"B\" for bytes, \"s\" for "
567
            "seconds, \"Hz\" for frequency).  Unknown suffixes are "
568
            "appended verbatim without scaling.")
569
        .with_example("B"_frag)
570
        .for_field(&external_log_format::value_def::vd_meta,
571
                   &logline_value_meta::lvm_unit_suffix),
572

573
    yajlpp::property_handler("divisor")
574
        .with_synopsis("<number>")
575
        .with_exclusive_min_value(0)
576
        .with_description(
577
            "A divisor applied to the raw numeric value to normalize it "
578
            "to the base unit implied by the suffix.  For example, a "
579
            "field storing milliseconds paired with \"suffix\": \"s\" "
580
            "would declare \"divisor\": 1000.")
581
        .for_field(&external_log_format::value_def::vd_meta,
582
                   &logline_value_meta::lvm_unit_divisor),
583
};
584

585
static const json_path_container capture_highlight_handlers = {
586
    yajlpp::pattern_property_handler(R"((?<hl_cap_name>[^/]+))")
587
        .with_description("The definition of a capture highlight")
588
        .with_obj_provider<style_config,
589
                           std::map<intern_string_t, style_config>>(
590
            [](const yajlpp_provider_context& ypc,
324✔
591
               std::map<intern_string_t, style_config>* root) {
592
                auto* retval = &(*root)[ypc.get_substr_i(0)];
324✔
593

594
                return retval;
324✔
595
            })
596
        .with_children(style_config_handlers),
597
};
598

599
static const json_path_container highlighter_def_handlers = {
600
    yajlpp::property_handler("pattern")
601
        .with_synopsis("<regex>")
602
        .with_description(
603
            "A regular expression to highlight in logs of this format.")
604
        .for_field(&external_log_format::highlighter_def::hd_pattern),
605

606
    yajlpp::property_handler("base-style")
607
        .with_description("The style to use for the entire pattern")
608
        .for_child(&external_log_format::highlighter_def::hd_base_style)
609
        .with_children(style_config_handlers),
610

611
    yajlpp::property_handler("captures")
612
        .with_description(
613
            "Styles for individual named capture groups in the pattern")
614
        .for_child(&external_log_format::highlighter_def::hd_capture_styles)
615
        .with_children(capture_highlight_handlers),
616
};
617

618
static const json_path_container legacy_highlight_handlers = {
619
    yajlpp::property_handler("pattern")
620
        .with_synopsis("<regex>")
621
        .with_description(
622
            "A regular expression to highlight in logs of this format.")
623
        .for_field(&external_log_format::highlighter_def::hd_pattern),
624
    yajlpp::property_handler("color")
625
        .with_synopsis("#<hex>|<name>")
626
        .with_description("The color to use when highlighting this pattern.")
627
        .for_field(&external_log_format::highlighter_def::hd_base_style,
628
                   &style_config::sc_color),
629
    yajlpp::property_handler("background-color")
630
        .with_synopsis("#<hex>|<name>")
631
        .with_description(
632
            "The background color to use when highlighting this pattern.")
633
        .for_field(&external_log_format::highlighter_def::hd_base_style,
634
                   &style_config::sc_background_color),
635
    yajlpp::property_handler("underline")
636
        .with_synopsis("<enabled>")
637
        .with_description("Highlight this pattern with an underline.")
638
        .for_field(&external_log_format::highlighter_def::hd_base_style,
639
                   &style_config::sc_underline),
640
    yajlpp::property_handler("blink")
641
        .with_synopsis("<enabled>")
642
        .with_description("Highlight this pattern by blinking.")
643
        .for_field(&external_log_format::highlighter_def::hd_base_style,
644
                   &style_config::sc_blink),
645
    yajlpp::property_handler("nestable")
646
        .with_synopsis("<enabled>")
647
        .with_description("This highlight can be nested in another highlight.")
648
        .for_field(&external_log_format::highlighter_def::hd_base_style,
649
                   &style_config::sc_nestable),
650
};
651

652
static const json_path_container legacy_highlight_def_handlers = {
653
    yajlpp::pattern_property_handler(R"((?<highlight_name>[^/]+))")
654
        .with_description("The definition of a highlight")
655
        .with_obj_provider<external_log_format::highlighter_def,
656
                           external_log_format>(
657
            [](const yajlpp_provider_context& ypc, external_log_format* root) {
13,686✔
658
                auto* retval
659
                    = &(root->elf_highlighter_patterns[ypc.get_substr_i(0)]);
13,686✔
660

661
                return retval;
13,686✔
662
            })
663
        .with_children(legacy_highlight_handlers),
664
};
665

666
static const json_path_container value_highlight_handlers = {
667
    yajlpp::pattern_property_handler(R"((?<highlight_name>[^/]+))")
668
        .with_description("The definition of a highlight")
669
        .with_obj_provider<external_log_format::highlighter_def,
670
                           external_log_format::value_def>(
671
            [](const yajlpp_provider_context& ypc,
29,166✔
672
               external_log_format::value_def* root) {
673
                auto* retval
674
                    = &(root->vd_highlighter_patterns[ypc.get_substr_i(0)]);
29,166✔
675

676
                return retval;
29,166✔
677
            })
678
        .with_children(highlighter_def_handlers),
679
};
680

681
static const struct json_path_container value_def_handlers = {
682
    yajlpp::property_handler("kind")
683
        .with_synopsis("<data-type>")
684
        .with_description("The type of data in the field")
685
        .with_enum_values(KIND_ENUM)
686
        .for_field(&external_log_format::value_def::vd_meta,
687
                   &logline_value_meta::lvm_kind),
688

689
    yajlpp::property_handler("collate")
690
        .with_synopsis("<function>")
691
        .with_description("The collating function to use for this column")
692
        .for_field(&external_log_format::value_def::vd_collate),
693

694
    yajlpp::property_handler("unit")
695
        .with_description("Unit definitions for this field")
696
        .with_children(unit_handlers),
697

698
    yajlpp::property_handler("identifier")
699
        .with_synopsis("<bool>")
700
        .with_description("Indicates whether or not this field contains an "
701
                          "identifier that should be highlighted")
702
        .for_field(&external_log_format::value_def::vd_meta,
703
                   &logline_value_meta::lvm_identifier),
704

705
    yajlpp::property_handler("foreign-key")
706
        .with_synopsis("<bool>")
707
        .with_description("Indicates whether or not this field should be "
708
                          "treated as a foreign key for row in another table")
709
        .for_field(&external_log_format::value_def::vd_meta,
710
                   &logline_value_meta::lvm_foreign_key),
711

712
    yajlpp::property_handler("hidden")
713
        .with_synopsis("<bool>")
714
        .with_description(
715
            "Indicates whether or not this field should be hidden")
716
        .for_field(&external_log_format::value_def::vd_meta,
717
                   &logline_value_meta::lvm_hidden),
718

719
    yajlpp::property_handler("action-list#")
720
        .with_synopsis("<string>")
721
        .with_description("Actions to execute when this field is clicked on")
722
        .for_field(&external_log_format::value_def::vd_action_list),
723

724
    yajlpp::property_handler("rewriter")
725
        .with_synopsis("<command>")
726
        .with_description(
727
            "A command that will rewrite this field when pretty-printing")
728
        .for_field(&external_log_format::value_def::vd_rewriter)
729
        .with_example(
730
            ";SELECT :sc_status || ' (' || (SELECT message FROM "
731
            "http_status_codes WHERE status = :sc_status) || ') '"_frag),
732

733
    yajlpp::property_handler("description")
734
        .with_synopsis("<string>")
735
        .with_description("A description of the field")
736
        .for_field(&external_log_format::value_def::vd_description),
737

738
    yajlpp::property_handler("highlights")
739
        .with_description("The set of highlight definitions")
740
        .with_children(value_highlight_handlers),
741
};
742

743
static const struct json_path_container sample_handlers = {
744
    yajlpp::property_handler("description")
745
        .with_synopsis("<text>")
746
        .with_description("A description of this sample.")
747
        .for_field(&external_log_format::sample_t::s_description),
748
    yajlpp::property_handler("line")
749
        .with_synopsis("<log-line>")
750
        .with_description(
751
            "A sample log line that should match a pattern in this format.")
752
        .for_field(&external_log_format::sample_t::s_line),
753

754
    yajlpp::property_handler("level")
755
        .with_enum_values(LEVEL_ENUM)
756
        .with_description("The expected level for this sample log line.")
757
        .for_field(&external_log_format::sample_t::s_level),
758
};
759

760
static constexpr json_path_handler_base::enum_value_t TYPE_ENUM[] = {
761
    {"text"_frag, external_log_format::elf_type_t::ELF_TYPE_TEXT},
762
    {"json"_frag, external_log_format::elf_type_t::ELF_TYPE_JSON},
763
    {"tabular"_frag, external_log_format::elf_type_t::ELF_TYPE_TABULAR},
764

765
    json_path_handler_base::ENUM_TERMINATOR,
766
};
767

768
static const struct json_path_container regex_handlers = {
769
    yajlpp::pattern_property_handler(R"((?<pattern_name>[^/]+))")
770
        .with_description("The set of patterns used to match log messages")
771
        .with_obj_provider(pattern_provider)
772
        .with_children(pattern_handlers),
773
};
774

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

787
static const struct json_path_container value_handlers = {
788
    yajlpp::pattern_property_handler("(?<value_name>[^/]+)")
789
        .with_description(
790
            "The set of values captured by the log message patterns")
791
        .with_obj_provider(value_def_provider)
792
        .with_children(value_def_handlers),
793
};
794

795
static const struct json_path_container tag_path_handlers = {
796
    yajlpp::property_handler("glob")
797
        .with_synopsis("<glob>")
798
        .with_description("The glob to match against file paths")
799
        .with_example("*/system.log*"_frag)
800
        .for_field(&format_tag_def::path_restriction::p_glob),
801
};
802

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

825
static const struct json_path_container tag_handlers = {
826
    yajlpp::pattern_property_handler(R"((?<tag_name>[\w:;\._\-]+))")
827
        .with_description("The name of the tag to apply")
828
        .with_obj_provider(format_tag_def_provider)
829
        .with_children(format_tag_def_handlers),
830
};
831

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

854
static const struct json_path_container partition_handlers = {
855
    yajlpp::pattern_property_handler(R"((?<partition_type>[\w:;\._\- ]+))")
856
        .with_description("The type of partition to apply")
857
        .with_obj_provider(format_partition_def_provider)
858
        .with_children(format_partition_def_handlers),
859
};
860

861
static const struct json_path_container action_def_handlers = {
862
    json_path_handler("label", read_action_def),
863
    json_path_handler("capture-output", read_action_bool),
864
    json_path_handler("cmd#", read_action_cmd),
865
};
866

867
static const struct json_path_container action_handlers = {
868
    json_path_handler(
869
        lnav::pcre2pp::code::from_const("(?<action_name>\\w+)").to_shared(),
870
        read_action_def)
871
        .with_children(action_def_handlers),
872
};
873

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

891
static const struct json_path_container search_table_handlers = {
892
    yajlpp::pattern_property_handler("(?<table_name>\\w+)")
893
        .with_description(
894
            "The set of search tables to be automatically defined")
895
        .with_obj_provider<external_log_format::search_table_def,
896
                           external_log_format>(
897
            [](const yajlpp_provider_context& ypc, external_log_format* root) {
45,553✔
898
                auto* retval = &(root->elf_search_tables[ypc.get_substr_i(0)]);
45,553✔
899

900
                return retval;
45,553✔
901
            })
902
        .with_children(search_table_def_handlers),
903
};
904

905
static const struct json_path_container header_expr_handlers = {
906
    yajlpp::pattern_property_handler(R"((?<header_expr_name>\w+))")
907
        .with_description("SQLite expression")
908
        .for_field(&external_log_format::header_exprs::he_exprs),
909
};
910

911
static const struct json_path_container header_handlers = {
912
    yajlpp::property_handler("expr")
913
        .with_description("The expressions used to check if a file header "
914
                          "matches this file format")
915
        .for_child(&external_log_format::header::h_exprs)
916
        .with_children(header_expr_handlers),
917
    yajlpp::property_handler("size")
918
        .with_description("The minimum size required for this header type")
919
        .for_field(&external_log_format::header::h_size),
920
};
921

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

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

959
static const struct json_path_container opid_description_format_handlers = {
960
    yajlpp::property_handler("format#")
961
        .with_description("Defines the elements of this operation description")
962
        .for_field(&log_format::opid_descriptors::od_descriptors)
963
        .with_children(opid_descriptor_handlers),
964
};
965

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

973
static const struct json_path_container subid_description_handlers = {
974
    yajlpp::pattern_property_handler(R"((?<subid_descriptor>[\w\.\-]+))")
975
        .with_description("A type of description for this sub-operation")
976
        .for_field(&log_format::lf_subid_description_def)
977
        .with_children(opid_description_format_handlers),
978
};
979

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

998
const struct json_path_container format_handlers = {
999
    yajlpp::property_handler("regex")
1000
        .with_description(
1001
            "The set of regular expressions used to match log messages")
1002
        .with_children(regex_handlers),
1003

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

1130
    yajlpp::property_handler("value")
1131
        .with_description("The set of value definitions")
1132
        .with_children(value_handlers),
1133

1134
    yajlpp::property_handler("tags")
1135
        .with_description("The tags to automatically apply to log messages")
1136
        .with_children(tag_handlers),
1137

1138
    yajlpp::property_handler("partitions")
1139
        .with_description(
1140
            "The partitions to automatically apply to log messages")
1141
        .with_children(partition_handlers),
1142

1143
    yajlpp::property_handler("action").with_children(action_handlers),
1144
    yajlpp::property_handler("sample#")
1145
        .with_description("An array of sample log messages to be tested "
1146
                          "against the log message patterns")
1147
        .with_obj_provider(sample_provider)
1148
        .with_children(sample_handlers),
1149

1150
    yajlpp::property_handler("line-format#")
1151
        .with_description("The display format for JSON-encoded log messages")
1152
        .with_obj_provider(line_format_provider)
1153
        .add_cb(read_json_constant)
1154
        .with_children(line_format_handlers),
1155
    json_path_handler("search-table")
1156
        .with_description(
1157
            "Search tables to automatically define for this log format")
1158
        .with_children(search_table_handlers),
1159

1160
    yajlpp::property_handler("highlights")
1161
        .with_description("The set of highlight definitions")
1162
        .with_children(legacy_highlight_def_handlers),
1163

1164
    yajlpp::property_handler("file-type")
1165
        .with_synopsis("text|json|csv")
1166
        .with_description("The type of file that contains the log messages")
1167
        .with_enum_values(TYPE_ENUM)
1168
        .for_field(&external_log_format::elf_type),
1169

1170
    yajlpp::property_handler("max-unrecognized-lines")
1171
        .with_synopsis("<lines>")
1172
        .with_description("The maximum number of lines in a file to use when "
1173
                          "detecting the format")
1174
        .with_min_value(1)
1175
        .for_field(&log_format::lf_max_unrecognized_lines),
1176
};
1177

1178
static int
1179
read_id(yajlpp_parse_context* ypc,
67,117✔
1180
        const unsigned char* str,
1181
        size_t len,
1182
        yajl_string_props_t*)
1183
{
1184
    auto* ud = static_cast<loader_userdata*>(ypc->ypc_userdata);
67,117✔
1185
    auto file_id = std::string((const char*) str, len);
67,117✔
1186

1187
    ud->ud_file_schema = file_id;
67,117✔
1188
    if (SUPPORTED_FORMAT_SCHEMAS.find(file_id)
67,117✔
1189
        == SUPPORTED_FORMAT_SCHEMAS.end())
134,234✔
1190
    {
1191
        const auto* handler = ypc->ypc_current_handler;
1✔
1192
        attr_line_t notes{"expecting one of the following $schema values:"};
1✔
1193

1194
        for (const auto& schema : SUPPORTED_FORMAT_SCHEMAS) {
2✔
1195
            notes.append("\n").append(
2✔
1196
                lnav::roles::symbol(fmt::format(FMT_STRING("  {}"), schema)));
5✔
1197
        }
1198
        ypc->report_error(
2✔
UNCOV
1199
            lnav::console::user_message::error(
×
1200
                attr_line_t("'")
1✔
1201
                    .append(lnav::roles::symbol(file_id))
2✔
1202
                    .append("' is not a supported log format $schema version"))
1✔
1203
                .with_snippet(ypc->get_snippet())
2✔
1204
                .with_note(notes)
1✔
1205
                .with_help(handler->get_help_text(ypc)));
1✔
1206
    }
1✔
1207

1208
    return 1;
67,117✔
1209
}
67,117✔
1210

1211
const struct json_path_container root_format_handler = json_path_container{
1212
    json_path_handler("$schema", read_id)
1213
        .with_synopsis("The URI of the schema for this file")
1214
        .with_description("Specifies the type of this file"),
1215

1216
    yajlpp::pattern_property_handler("(?<format_name>\\w+)")
1217
        .with_description("The definition of a log file format.")
1218
        .with_obj_provider(ensure_format)
1219
        .with_children(format_handlers),
1220
}
1221
    .with_schema_id(DEFAULT_FORMAT_SCHEMA);
1222

1223
static void
1224
write_sample_file()
911✔
1225
{
1226
    const auto dstdir = lnav::paths::dotlnav();
911✔
1227
    for (const auto& bsf : lnav_format_json) {
66,503✔
1228
        auto sample_path = dstdir
1229
            / fmt::format(FMT_STRING("formats/default/{}.sample"),
262,368✔
1230
                          bsf.get_name());
131,184✔
1231

1232
        const auto& name_sf = bsf.get_name();
65,592✔
1233
        auto stat_res = lnav::filesystem::stat_file(sample_path);
65,592✔
1234
        if (stat_res.isOk()) {
65,592✔
1235
            auto st = stat_res.unwrap();
56,952✔
1236
            if (st.st_mtime >= lnav::filesystem::self_mtime()) {
56,952✔
1237
                log_debug("skipping writing sample: %.*s (mtimes %ld >= %lld)",
56,952✔
1238
                          name_sf.length(),
1239
                          name_sf.data(),
1240
                          st.st_mtime,
1241
                          lnav::filesystem::self_mtime());
1242
                continue;
56,952✔
1243
            }
UNCOV
1244
            log_debug("sample file needs to be updated: %.*s",
×
1245
                      name_sf.length(),
1246
                      name_sf.data());
1247
        } else {
1248
            log_debug("sample file does not exist: %.*s",
8,640✔
1249
                      name_sf.length(),
1250
                      name_sf.data());
1251
        }
1252

1253
        auto sfp = bsf.to_string_fragment_producer();
8,640✔
1254
        auto write_res = lnav::filesystem::write_file(
1255
            sample_path,
1256
            *sfp,
8,640✔
1257
            {lnav::filesystem::write_file_options::read_only});
17,280✔
1258

1259
        if (write_res.isErr()) {
8,640✔
1260
            auto msg = write_res.unwrapErr();
4,896✔
1261
            fprintf(stderr,
4,896✔
1262
                    "error:unable to write default format file: %s -- %s\n",
1263
                    sample_path.c_str(),
1264
                    msg.c_str());
1265
        }
4,896✔
1266
    }
122,544✔
1267

1268
    for (const auto& bsf : lnav_sh_scripts) {
4,555✔
1269
        auto sh_path = dstdir
1270
            / fmt::format(FMT_STRING("formats/default/{}"), bsf.get_name());
14,576✔
1271
        auto stat_res = lnav::filesystem::stat_file(sh_path);
3,644✔
1272
        if (stat_res.isOk()) {
3,644✔
1273
            auto st = stat_res.unwrap();
3,164✔
1274
            if (st.st_mtime >= lnav::filesystem::self_mtime()) {
3,164✔
1275
                continue;
3,164✔
1276
            }
1277
        }
1278

1279
        auto sfp = bsf.to_string_fragment_producer();
480✔
1280
        auto write_res = lnav::filesystem::write_file(
1281
            sh_path,
1282
            *sfp,
480✔
1283
            {
1284
                lnav::filesystem::write_file_options::executable,
1285
                lnav::filesystem::write_file_options::read_only,
1286
            });
960✔
1287

1288
        if (write_res.isErr()) {
480✔
1289
            auto msg = write_res.unwrapErr();
272✔
1290
            fprintf(stderr,
272✔
1291
                    "error:unable to write default text file: %s -- %s\n",
1292
                    sh_path.c_str(),
1293
                    msg.c_str());
1294
        }
272✔
1295
    }
6,808✔
1296

1297
    for (const auto& bsf : lnav_scripts) {
20,042✔
1298
        script_metadata meta;
19,131✔
1299
        auto sf = bsf.to_string_fragment_producer()->to_string();
19,131✔
1300

1301
        meta.sm_name = bsf.get_name().to_string();
19,131✔
1302
        extract_metadata(sf, meta);
19,131✔
1303
        auto path
1304
            = fmt::format(FMT_STRING("formats/default/{}.lnav"), meta.sm_name);
57,393✔
1305
        auto script_path = dstdir / path;
19,131✔
1306
        auto stat_res = lnav::filesystem::stat_file(script_path);
19,131✔
1307
        if (stat_res.isOk()) {
19,131✔
1308
            auto st = stat_res.unwrap();
16,663✔
1309
            if (st.st_mtime >= lnav::filesystem::self_mtime()) {
16,663✔
1310
                continue;
16,663✔
1311
            }
1312
        }
1313

1314
        auto write_res = lnav::filesystem::write_file(
1315
            script_path,
1316
            sf,
1317
            {
1318
                lnav::filesystem::write_file_options::executable,
1319
                lnav::filesystem::write_file_options::read_only,
1320
            });
2,468✔
1321
        if (write_res.isErr()) {
2,468✔
1322
            fprintf(stderr,
1,428✔
1323
                    "error:unable to write default script file: %s -- %s\n",
1324
                    script_path.c_str(),
1325
                    strerror(errno));
1,428✔
1326
        }
1327
    }
85,783✔
1328
}
911✔
1329

1330
static void
1331
format_error_reporter(const yajlpp_parse_context& ypc,
16✔
1332
                      const lnav::console::user_message& msg)
1333
{
1334
    auto* ud = (loader_userdata*) ypc.ypc_userdata;
16✔
1335

1336
    ud->ud_errors->emplace_back(msg);
16✔
1337
}
16✔
1338

1339
std::vector<intern_string_t>
1340
load_format_file(const std::filesystem::path& filename,
1,527✔
1341
                 std::vector<lnav::console::user_message>& errors)
1342
{
1343
    std::vector<intern_string_t> retval;
1,527✔
1344
    loader_userdata ud;
1,527✔
1345
    auto_fd fd;
1,527✔
1346

1347
    log_info("loading formats from file: %s", filename.c_str());
1,527✔
1348
    yajlpp_parse_context ypc(intern_string::lookup(filename.string()),
3,054✔
1349
                             &root_format_handler);
1,527✔
1350
    ud.ud_parse_context = &ypc;
1,527✔
1351
    ud.ud_format_path = filename;
1,527✔
1352
    ud.ud_format_names = &retval;
1,527✔
1353
    ud.ud_errors = &errors;
1,527✔
1354
    ypc.ypc_userdata = &ud;
1,527✔
1355
    ypc.with_obj(ud);
1,527✔
1356
    if ((fd = lnav::filesystem::openp(filename, O_RDONLY)) == -1) {
1,527✔
UNCOV
1357
        errors.emplace_back(
×
UNCOV
1358
            lnav::console::user_message::error(
×
UNCOV
1359
                attr_line_t("unable to open format file: ")
×
UNCOV
1360
                    .append(lnav::roles::file(filename.string())))
×
1361
                .with_errno_reason());
1362
    } else {
1363
        auto_mem<yajl_handle_t> handle(yajl_free);
1,527✔
1364
        char buffer[2048];
1365
        off_t offset = 0;
1,527✔
1366
        ssize_t rc = -1;
1,527✔
1367

1368
        handle = yajl_alloc(&ypc.ypc_callbacks, nullptr, &ypc);
1,527✔
1369
        ypc.with_handle(handle).with_error_reporter(format_error_reporter);
1,527✔
1370
        yajl_config(handle, yajl_allow_comments, 1);
1,527✔
1371
        while (true) {
1372
            rc = read(fd, buffer, sizeof(buffer));
3,269✔
1373
            if (rc == 0) {
3,269✔
1374
                break;
1,526✔
1375
            }
1376
            if (rc == -1) {
1,743✔
UNCOV
1377
                errors.emplace_back(
×
UNCOV
1378
                    lnav::console::user_message::error(
×
UNCOV
1379
                        attr_line_t("unable to read format file: ")
×
UNCOV
1380
                            .append(lnav::roles::file(filename.string())))
×
1381
                        .with_errno_reason());
UNCOV
1382
                break;
×
1383
            }
1384
            if (offset == 0 && (rc > 2) && (buffer[0] == '#')
1,743✔
UNCOV
1385
                && (buffer[1] == '!'))
×
1386
            {
1387
                // Turn it into a JavaScript comment.
UNCOV
1388
                buffer[0] = buffer[1] = '/';
×
1389
            }
1390
            if (ypc.parse((const unsigned char*) buffer, rc) != yajl_status_ok)
1,743✔
1391
            {
1392
                break;
1✔
1393
            }
1394
            offset += rc;
1,742✔
1395
        }
1396
        if (rc == 0) {
1,527✔
1397
            ypc.complete_parse();
1,526✔
1398
        }
1399

1400
        if (ud.ud_file_schema.empty()) {
1,527✔
1401
            static const auto SCHEMA_LINE
1402
                = attr_line_t()
2✔
1403
                      .append(
4✔
1404
                          fmt::format(FMT_STRING("    \"$schema\": \"{}\","),
8✔
1405
                                      *SUPPORTED_FORMAT_SCHEMAS.begin()))
4✔
1406
                      .with_attr_for_all(VC_ROLE.value(role_t::VCR_QUOTED_CODE))
4✔
1407
                      .move();
4✔
1408

1409
            errors.emplace_back(
2✔
UNCOV
1410
                lnav::console::user_message::warning(
×
1411
                    attr_line_t("format file is missing ")
4✔
1412
                        .append_quoted("$schema"_symbol)
2✔
1413
                        .append(" property"))
2✔
1414
                    .with_snippet(lnav::console::snippet::from(
6✔
1415
                        intern_string::lookup(filename.string()), ""))
4✔
1416
                    .with_note("the schema specifies the supported format "
4✔
1417
                               "version and can be used with editors to "
1418
                               "automatically validate the file")
1419
                    .with_help(attr_line_t("add the following property to the "
4✔
1420
                                           "top-level JSON object:\n")
1421
                                   .append(SCHEMA_LINE)));
2✔
1422
        }
1423
    }
1,527✔
1424

1425
    return retval;
3,054✔
1426
}
1,527✔
1427

1428
static void
1429
load_from_path(const std::filesystem::path& path,
2,498✔
1430
               std::vector<lnav::console::user_message>& errors)
1431
{
1432
    auto format_path = path / "formats/*/*.json";
2,498✔
1433
    static_root_mem<glob_t, globfree> gl;
2,498✔
1434

1435
    log_info("loading formats from path: %s", format_path.c_str());
2,498✔
1436
    if (glob(format_path.c_str(), 0, nullptr, gl.inout()) == 0) {
2,498✔
1437
        for (int lpc = 0; lpc < (int) gl->gl_pathc; lpc++) {
1,634✔
1438
            auto filepath = std::filesystem::path(gl->gl_pathv[lpc]);
1,524✔
1439

1440
            if (startswith(filepath.filename().string(), "config.")) {
1,524✔
UNCOV
1441
                log_info("  not loading config as format: %s",
×
1442
                         filepath.c_str());
UNCOV
1443
                continue;
×
1444
            }
1445

1446
            auto format_list = load_format_file(filepath, errors);
1,524✔
1447
            if (format_list.empty()) {
1,524✔
1448
                log_warning("Empty format file: %s", filepath.c_str());
2✔
1449
            } else {
1450
                log_info("contents of format file '%s':", filepath.c_str());
1,522✔
1451
                for (auto iter = format_list.begin(); iter != format_list.end();
3,152✔
1452
                     ++iter)
1,630✔
1453
                {
1454
                    log_info("  found format: %s", iter->get());
1,630✔
1455
                }
1456
            }
1457
        }
1,524✔
1458
    }
1459
}
2,498✔
1460

1461
void
1462
load_formats(const std::vector<std::filesystem::path>& extra_paths,
911✔
1463
             std::vector<lnav::console::user_message>& errors)
1464
{
1465
    auto op_guard = lnav_opid_guard::once(__FUNCTION__);
911✔
1466

1467
    auto default_source = lnav::paths::dotlnav() / "default";
911✔
1468
    std::vector<intern_string_t> retval;
911✔
1469
    loader_userdata ud;
911✔
1470
    yajl_handle handle;
1471

1472
    write_sample_file();
911✔
1473

1474
    log_debug("Loading default formats");
911✔
1475
    for (const auto& bsf : lnav_format_json) {
66,503✔
1476
        yajlpp_parse_context ypc_builtin(intern_string::lookup(bsf.get_name()),
65,592✔
1477
                                         &root_format_handler);
65,592✔
1478
        handle = yajl_alloc(&ypc_builtin.ypc_callbacks, nullptr, &ypc_builtin);
65,592✔
1479
        ud.ud_parse_context = &ypc_builtin;
65,592✔
1480
        ud.ud_format_names = &retval;
65,592✔
1481
        ud.ud_errors = &errors;
65,592✔
1482
        ypc_builtin.with_obj(ud)
131,184✔
1483
            .with_handle(handle)
65,592✔
1484
            .with_error_reporter(format_error_reporter)
65,592✔
1485
            .ypc_userdata = &ud;
131,184✔
1486
        yajl_config(handle, yajl_allow_comments, 1);
65,592✔
1487
        auto sf = bsf.to_string_fragment_producer();
65,592✔
1488
        ypc_builtin.parse(*sf);
65,592✔
1489
        yajl_free(handle);
65,592✔
1490
    }
65,592✔
1491

1492
    for (const auto& extra_path : extra_paths) {
3,409✔
1493
        load_from_path(extra_path, errors);
2,498✔
1494
    }
1495

1496
    std::vector<std::shared_ptr<external_log_format>> alpha_ordered_formats;
911✔
1497
    for (auto iter = LOG_FORMATS.begin(); iter != LOG_FORMATS.end(); ++iter) {
68,025✔
1498
        auto& elf = iter->second;
67,114✔
1499
        elf->build(errors);
67,114✔
1500

1501
        for (auto& check_iter : LOG_FORMATS) {
5,030,142✔
1502
            if (iter->first == check_iter.first) {
4,963,028✔
1503
                continue;
67,114✔
1504
            }
1505

1506
            auto& check_elf = check_iter.second;
4,895,914✔
1507
            if (elf->match_samples(check_elf->elf_samples)) {
4,895,914✔
1508
                log_warning(
19,057✔
1509
                    "Format collision, format '%s' matches sample from '%s'",
1510
                    elf->get_name().get(),
1511
                    check_elf->get_name().get());
1512
                elf->elf_collision.push_back(check_elf->get_name());
19,057✔
1513
            }
1514
        }
1515

1516
        alpha_ordered_formats.push_back(elf);
67,114✔
1517
    }
1518

1519
    auto& graph_ordered_formats = external_log_format::GRAPH_ORDERED_FORMATS;
911✔
1520

1521
    while (!alpha_ordered_formats.empty()) {
11,587✔
1522
        std::vector<intern_string_t> popped_formats;
10,676✔
1523

1524
        for (auto iter = alpha_ordered_formats.begin();
10,676✔
1525
             iter != alpha_ordered_formats.end();)
122,541✔
1526
        {
1527
            auto elf = *iter;
111,865✔
1528
            if (elf->elf_collision.empty()) {
111,865✔
1529
                iter = alpha_ordered_formats.erase(iter);
67,114✔
1530
                popped_formats.push_back(elf->get_name());
67,114✔
1531
                graph_ordered_formats.push_back(elf);
67,114✔
1532
            } else {
1533
                ++iter;
44,751✔
1534
            }
1535
        }
111,865✔
1536

1537
        if (popped_formats.empty() && !alpha_ordered_formats.empty()) {
10,676✔
1538
            bool broke_cycle = false;
3,060✔
1539

1540
            log_warning("Detected a cycle...");
3,060✔
1541
            for (const auto& elf : alpha_ordered_formats) {
17,917✔
1542
                if (elf->elf_builtin_format) {
15,184✔
1543
                    log_warning("  Skipping builtin format -- %s",
14,857✔
1544
                                elf->get_name().get());
1545
                } else {
1546
                    log_warning("  Breaking cycle by picking -- %s",
327✔
1547
                                elf->get_name().get());
1548
                    elf->elf_collision.clear();
327✔
1549
                    broke_cycle = true;
327✔
1550
                    break;
327✔
1551
                }
1552
            }
1553
            if (!broke_cycle) {
3,060✔
1554
                alpha_ordered_formats.front()->elf_collision.clear();
2,733✔
1555
            }
1556
        }
1557

1558
        for (const auto& elf : alpha_ordered_formats) {
55,427✔
1559
            for (auto& popped_format : popped_formats) {
514,338✔
1560
                elf->elf_collision.remove(popped_format);
469,587✔
1561
            }
1562
        }
1563
    }
10,676✔
1564

1565
    log_info("Format order:");
911✔
1566
    for (auto& graph_ordered_format : graph_ordered_formats) {
68,025✔
1567
        log_info("  %s", graph_ordered_format->get_name().get());
67,114✔
1568
    }
1569

1570
    auto& roots = log_format::get_root_formats();
911✔
1571
    auto iter = std::find_if(roots.begin(), roots.end(), [](const auto& elem) {
911✔
1572
        return elem->get_name() == "generic_log";
5,466✔
1573
    });
1574
    roots.insert(
911✔
1575
        iter, graph_ordered_formats.begin(), graph_ordered_formats.end());
1576
}
911✔
1577

1578
static void
1579
exec_sql_in_path(sqlite3* db,
2,451✔
1580
                 const std::map<std::string, scoped_value_t>& global_vars,
1581
                 const std::filesystem::path& path,
1582
                 std::vector<lnav::console::user_message>& errors)
1583
{
1584
    auto format_path = path / "formats/*/*.sql";
2,451✔
1585
    static_root_mem<glob_t, globfree> gl;
2,451✔
1586

1587
    log_info("executing SQL files in path: %s", format_path.c_str());
2,451✔
1588
    if (glob(format_path.c_str(), 0, nullptr, gl.inout()) == 0) {
2,451✔
1589
        for (int lpc = 0; lpc < (int) gl->gl_pathc; lpc++) {
1,717✔
1590
            auto filename = std::filesystem::path(gl->gl_pathv[lpc]);
859✔
1591
            auto read_res = lnav::filesystem::read_file(filename);
859✔
1592

1593
            if (read_res.isOk()) {
859✔
1594
                log_info("Executing SQL file: %s", filename.c_str());
859✔
1595
                auto content = read_res.unwrap();
859✔
1596

1597
                sql_execute_script(
859✔
1598
                    db, global_vars, filename.c_str(), content.c_str(), errors);
1599
            } else {
859✔
UNCOV
1600
                errors.emplace_back(
×
UNCOV
1601
                    lnav::console::user_message::error(
×
UNCOV
1602
                        attr_line_t("unable to read format file: ")
×
UNCOV
1603
                            .append(lnav::roles::file(filename.string())))
×
UNCOV
1604
                        .with_reason(read_res.unwrapErr()));
×
1605
            }
1606
        }
859✔
1607
    }
1608
}
2,451✔
1609

1610
void
1611
load_format_extra(sqlite3* db,
796✔
1612
                  const std::map<std::string, scoped_value_t>& global_vars,
1613
                  const std::vector<std::filesystem::path>& extra_paths,
1614
                  std::vector<lnav::console::user_message>& errors)
1615
{
1616
    for (const auto& extra_path : extra_paths) {
3,247✔
1617
        exec_sql_in_path(db, global_vars, extra_path, errors);
2,451✔
1618
    }
1619
}
796✔
1620

1621
static void
1622
extract_metadata(string_fragment contents, script_metadata& meta_out)
19,998✔
1623
{
1624
    static const auto SYNO_RE = lnav::pcre2pp::code::from_const(
1625
        "^#\\s+@synopsis:(.*)$", PCRE2_MULTILINE);
19,998✔
1626
    static const auto DESC_RE = lnav::pcre2pp::code::from_const(
1627
        "^#\\s+@description:(.*)$", PCRE2_MULTILINE);
19,998✔
1628
    static const auto OUTPUT_FORMAT_RE = lnav::pcre2pp::code::from_const(
1629
        "^#\\s+@output-format:\\s+(.*)$", PCRE2_MULTILINE);
19,998✔
1630

1631
    auto syno_md = SYNO_RE.create_match_data();
19,998✔
1632
    auto syno_match_res
1633
        = SYNO_RE.capture_from(contents).into(syno_md).matches().ignore_error();
19,998✔
1634
    if (syno_match_res) {
19,998✔
1635
        meta_out.sm_synopsis = syno_md[1]->trim().to_string();
19,959✔
1636
    }
1637
    auto desc_md = DESC_RE.create_match_data();
19,998✔
1638
    auto desc_match_res
1639
        = DESC_RE.capture_from(contents).into(desc_md).matches().ignore_error();
19,998✔
1640
    if (desc_match_res) {
19,998✔
1641
        meta_out.sm_description = desc_md[1]->trim().to_string();
19,959✔
1642
    }
1643

1644
    auto out_format_md = OUTPUT_FORMAT_RE.create_match_data();
19,998✔
1645
    auto out_format_res = OUTPUT_FORMAT_RE.capture_from(contents)
19,998✔
1646
                              .into(out_format_md)
19,998✔
1647
                              .matches()
39,996✔
1648
                              .ignore_error();
19,998✔
1649
    if (out_format_res) {
19,998✔
1650
        auto out_format_frag = out_format_md[1]->trim();
952✔
1651
        auto from_res = from<text_format_t>(out_format_frag);
952✔
1652
        if (from_res.isErr()) {
952✔
UNCOV
1653
            log_error("%s (%s): invalid @output-format '%.*s'",
×
1654
                      meta_out.sm_name.c_str(),
1655
                      meta_out.sm_path.c_str(),
1656
                      out_format_frag.length(),
1657
                      out_format_frag.data());
1658
        } else {
1659
            meta_out.sm_output_format = from_res.unwrap();
952✔
1660
            log_info("%s (%s): setting output format to %d",
952✔
1661
                     meta_out.sm_name.c_str(),
1662
                     meta_out.sm_path.c_str(),
1663
                     meta_out.sm_output_format);
1664
        }
1665
    }
952✔
1666

1667
    if (!meta_out.sm_synopsis.empty()) {
19,998✔
1668
        size_t space = meta_out.sm_synopsis.find(' ');
19,959✔
1669

1670
        if (space == std::string::npos) {
19,959✔
1671
            space = meta_out.sm_synopsis.size();
15,240✔
1672
        }
1673
        meta_out.sm_name = meta_out.sm_synopsis.substr(0, space);
19,959✔
1674
    }
1675
}
19,998✔
1676

1677
void
1678
extract_metadata_from_file(struct script_metadata& meta_inout)
867✔
1679
{
1680
    auto stat_res = lnav::filesystem::stat_file(meta_inout.sm_path);
867✔
1681
    if (stat_res.isErr()) {
867✔
UNCOV
1682
        log_warning("unable to open script: %s -- %s",
×
1683
                    meta_inout.sm_path.c_str(),
1684
                    stat_res.unwrapErr().c_str());
UNCOV
1685
        return;
×
1686
    }
1687

1688
    auto st = stat_res.unwrap();
867✔
1689
    if (!S_ISREG(st.st_mode)) {
867✔
UNCOV
1690
        log_warning("script is not a regular file -- %s",
×
1691
                    meta_inout.sm_path.c_str());
1692
        return;
×
1693
    }
1694

1695
    auto open_res = lnav::filesystem::open_file(meta_inout.sm_path, O_RDONLY);
867✔
1696
    if (open_res.isErr()) {
867✔
UNCOV
1697
        log_warning("unable to open script file: %s -- %s",
×
1698
                    meta_inout.sm_path.c_str(),
1699
                    open_res.unwrapErr().c_str());
UNCOV
1700
        return;
×
1701
    }
1702

1703
    auto fd = open_res.unwrap();
867✔
1704
    char buffer[8 * 1024];
1705
    auto rc = read(fd, buffer, sizeof(buffer));
867✔
1706
    if (rc > 0) {
867✔
1707
        extract_metadata(string_fragment::from_bytes(buffer, rc), meta_inout);
867✔
1708
    }
1709
}
867✔
1710

1711
static void
1712
find_format_in_path(const std::filesystem::path& path,
131✔
1713
                    available_scripts& scripts)
1714
{
1715
    for (const auto& format_path :
393✔
1716
         {path / "formats/*/*.lnav", path / "configs/*/*.lnav"})
786✔
1717
    {
1718
        static_root_mem<glob_t, globfree> gl;
262✔
1719

1720
        log_debug("Searching for script in path: %s", format_path.c_str());
262✔
1721
        if (glob(format_path.c_str(), 0, nullptr, gl.inout()) == 0) {
262✔
1722
            for (size_t lpc = 0; lpc < gl->gl_pathc; lpc++) {
917✔
1723
                const char* filename = basename(gl->gl_pathv[lpc]);
860✔
1724
                auto script_name = std::string(filename, strlen(filename) - 5);
860✔
1725
                struct script_metadata meta;
860✔
1726

1727
                meta.sm_path = gl->gl_pathv[lpc];
860✔
1728
                meta.sm_name = script_name;
860✔
1729
                extract_metadata_from_file(meta);
860✔
1730
                scripts.as_scripts[script_name].push_back(meta);
860✔
1731

1732
                log_info("  found script: %s", meta.sm_path.c_str());
860✔
1733
            }
860✔
1734
        }
1735
    }
655✔
1736
}
131✔
1737

1738
available_scripts
1739
find_format_scripts(const std::vector<std::filesystem::path>& extra_paths)
41✔
1740
{
1741
    available_scripts retval;
41✔
1742
    for (const auto& extra_path : extra_paths) {
172✔
1743
        find_format_in_path(extra_path, retval);
131✔
1744
    }
1745
    return retval;
41✔
UNCOV
1746
}
×
1747

1748
void
1749
load_format_vtabs(log_vtab_manager* vtab_manager,
796✔
1750
                  std::vector<lnav::console::user_message>& errors)
1751
{
1752
    auto& root_formats = LOG_FORMATS;
796✔
1753

1754
    for (auto& root_format : root_formats) {
58,972✔
1755
        root_format.second->register_vtabs(vtab_manager, errors);
58,176✔
1756
    }
1757
}
796✔
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