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

tstack / lnav / 25203625092-3018

01 May 2026 05:26AM UTC coverage: 69.234% (-0.02%) from 69.25%
25203625092-3018

push

github

tstack
[tests] add timeline test with uwsgi log

54421 of 78605 relevant lines covered (69.23%)

566059.35 hits per line

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

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

95
    if (find(formats->begin(), formats->end(), name) == formats->end()) {
4,732,813✔
96
        formats->push_back(name);
61,027✔
97
    }
98

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

110
    if (ud->ud_format_path.empty()) {
4,732,813✔
111
        retval->elf_builtin_format = true;
4,684,679✔
112
    }
113

114
    return retval;
4,732,813✔
115
}
116

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

123
    if (pat.get() == nullptr) {
335,436✔
124
        pat = std::make_shared<external_log_format::pattern>();
111,812✔
125
    }
126

127
    if (pat->p_config_path.empty()) {
335,436✔
128
        pat->p_name = intern_string::lookup(regex_name);
111,812✔
129
        pat->p_config_path = fmt::format(
223,624✔
130
            FMT_STRING("/{}/regex/{}"), elf->get_name(), regex_name);
559,060✔
131
    }
132

133
    return pat.get();
670,872✔
134
}
335,436✔
135

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

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

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

156
    return retval.get();
4,185,336✔
157
}
2,092,668✔
158

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

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

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

176
    return retval.get();
28,820✔
177
}
14,410✔
178

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

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

188
    if (iter == elf->lf_partition_defs.end()) {
13,672✔
189
        retval = std::make_shared<format_partition_def>(
5,178✔
190
            partition_name.to_string());
7,767✔
191
        elf->lf_partition_defs[partition_name] = retval;
2,589✔
192
    } else {
193
        retval = iter->second;
11,083✔
194
    }
195

196
    return retval.get();
27,344✔
197
}
13,672✔
198

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

206
    return &retval;
×
207
}
208

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

214
    return elf->jlf_line_format[index];
318,916✔
215
}
216

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

223
    jfe.jfe_type = external_log_format::json_log_field::VARIABLE;
273,134✔
224

225
    return &jfe;
273,134✔
226
}
227

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

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

238
    return 1;
3,933✔
239
}
3,933✔
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
        || field_name == "duration-divisor")
1✔
249
    {
250
        if (val <= 0) {
1✔
251
            ypc->report_error(
2✔
252
                lnav::console::user_message::error(
×
253
                    attr_line_t()
2✔
254
                        .append_quoted(fmt::to_string(val))
2✔
255
                        .append(" is not a valid value for ")
1✔
256
                        .append_quoted(lnav::roles::symbol(
2✔
257
                            ypc->get_full_path().to_string())))
2✔
258
                    .with_reason("value cannot be less than or equal to zero")
2✔
259
                    .with_snippet(ypc->get_snippet())
2✔
260
                    .with_help(ypc->ypc_current_handler->get_help_text(ypc)));
1✔
261
        }
262
        if (field_name == "timestamp-divisor") {
1✔
263
            elf->elf_timestamp_divisor = val;
1✔
264
        } else {
265
            elf->elf_duration_divisor = val;
×
266
        }
267
    }
268

269
    return 1;
1✔
270
}
1✔
271

272
static int
273
read_format_int(yajlpp_parse_context* ypc, long long val)
2,589✔
274
{
275
    auto elf = (external_log_format*) ypc->ypc_obj_stack.top();
2,589✔
276
    auto field_name = ypc->get_path_fragment(1);
2,589✔
277

278
    if (field_name == "timestamp-divisor"
2,589✔
279
        || field_name == "duration-divisor")
2,589✔
280
    {
281
        if (val <= 0) {
2,589✔
282
            ypc->report_error(
×
283
                lnav::console::user_message::error(
×
284
                    attr_line_t()
×
285
                        .append_quoted(fmt::to_string(val))
×
286
                        .append(" is not a valid value for ")
×
287
                        .append_quoted(lnav::roles::symbol(
×
288
                            ypc->get_full_path().to_string())))
×
289
                    .with_reason("value cannot be less than or equal to zero")
×
290
                    .with_snippet(ypc->get_snippet())
×
291
                    .with_help(ypc->ypc_current_handler->get_help_text(ypc)));
×
292
        }
293
        if (field_name == "timestamp-divisor") {
2,589✔
294
            elf->elf_timestamp_divisor = val;
1,760✔
295
        } else {
296
            elf->elf_duration_divisor = val;
829✔
297
        }
298
    }
299

300
    return 1;
2,589✔
301
}
2,589✔
302

303
static int
304
read_format_field(yajlpp_parse_context* ypc,
114,185✔
305
                  const unsigned char* str,
306
                  size_t len,
307
                  yajl_string_props_t*)
308
{
309
    auto elf = (external_log_format*) ypc->ypc_obj_stack.top();
114,185✔
310
    auto leading_slash = len > 0 && str[0] == '/';
114,185✔
311
    auto value = std::string((const char*) (leading_slash ? str + 1 : str),
312
                             leading_slash ? len - 1 : len);
114,185✔
313
    auto field_name = ypc->get_path_fragment(1);
114,185✔
314

315
    if (field_name == "timestamp-format") {
114,185✔
316
        elf->lf_timestamp_format.push_back(intern_string::lookup(value)->get());
12,643✔
317
    }
318

319
    return 1;
114,185✔
320
}
114,185✔
321

322
static int
323
read_levels(yajlpp_parse_context* ypc,
109,494✔
324
            const unsigned char* str,
325
            size_t len,
326
            yajl_string_props_t*)
327
{
328
    auto elf = (external_log_format*) ypc->ypc_obj_stack.top();
109,494✔
329
    auto regex = std::string((const char*) str, len);
109,494✔
330
    auto level_name_or_number = ypc->get_path_fragment(2);
109,494✔
331
    log_level_t level = string2level(level_name_or_number.c_str());
109,494✔
332
    auto value_frag = string_fragment::from_bytes(str, len);
109,494✔
333

334
    elf->elf_level_patterns[level].lp_pcre.pp_path = ypc->get_full_path();
109,494✔
335
    auto compile_res = lnav::pcre2pp::code::from(value_frag);
109,494✔
336
    if (compile_res.isErr()) {
109,494✔
337
        static const intern_string_t PATTERN_SRC
338
            = intern_string::lookup("pattern");
3✔
339
        auto ce = compile_res.unwrapErr();
1✔
340
        ypc->ypc_current_handler->report_error(
1✔
341
            ypc,
342
            value_frag.to_string(),
2✔
343
            lnav::console::to_user_message(PATTERN_SRC, ce));
2✔
344
    } else {
1✔
345
        elf->elf_level_patterns[level].lp_pcre.pp_value
109,493✔
346
            = compile_res.unwrap().to_shared();
218,986✔
347
    }
348

349
    return 1;
109,494✔
350
}
109,494✔
351

352
static int
353
read_level_int(yajlpp_parse_context* ypc, long long val)
14,297✔
354
{
355
    auto elf = (external_log_format*) ypc->ypc_obj_stack.top();
14,297✔
356
    auto level_name_or_number = ypc->get_path_fragment(2);
14,297✔
357
    log_level_t level = string2level(level_name_or_number.c_str());
14,297✔
358

359
    elf->elf_level_pairs.emplace_back(val, level);
14,297✔
360

361
    return 1;
14,297✔
362
}
14,297✔
363

364
static int
365
read_action_def(yajlpp_parse_context* ypc,
829✔
366
                const unsigned char* str,
367
                size_t len,
368
                yajl_string_props_t*)
369
{
370
    auto elf = (external_log_format*) ypc->ypc_obj_stack.top();
829✔
371
    auto action_name = ypc->get_path_fragment(2);
829✔
372
    auto field_name = ypc->get_path_fragment(3);
829✔
373
    auto val = std::string((const char*) str, len);
829✔
374

375
    elf->lf_action_defs[action_name].ad_name = action_name;
829✔
376
    if (field_name == "label") {
829✔
377
        elf->lf_action_defs[action_name].ad_label = val;
829✔
378
    }
379

380
    return 1;
829✔
381
}
829✔
382

383
static int
384
read_action_bool(yajlpp_parse_context* ypc, int val)
829✔
385
{
386
    auto elf = (external_log_format*) ypc->ypc_obj_stack.top();
829✔
387
    auto action_name = ypc->get_path_fragment(2);
829✔
388
    auto field_name = ypc->get_path_fragment(3);
829✔
389

390
    elf->lf_action_defs[action_name].ad_capture_output = val;
829✔
391

392
    return 1;
829✔
393
}
829✔
394

395
static int
396
read_action_cmd(yajlpp_parse_context* ypc,
829✔
397
                const unsigned char* str,
398
                size_t len,
399
                yajl_string_props_t*)
400
{
401
    auto elf = (external_log_format*) ypc->ypc_obj_stack.top();
829✔
402
    auto action_name = ypc->get_path_fragment(2);
829✔
403
    auto field_name = ypc->get_path_fragment(3);
829✔
404
    auto val = std::string((const char*) str, len);
829✔
405

406
    elf->lf_action_defs[action_name].ad_name = action_name;
829✔
407
    elf->lf_action_defs[action_name].ad_cmdline.push_back(val);
829✔
408

409
    return 1;
829✔
410
}
829✔
411

412
static external_log_format::sample_t&
413
ensure_sample(external_log_format* elf, int index)
244,962✔
414
{
415
    elf->elf_samples.resize(index + 1);
244,962✔
416

417
    return elf->elf_samples[index];
244,962✔
418
}
419

420
static external_log_format::sample_t*
421
sample_provider(const yajlpp_provider_context& ypc, external_log_format* elf)
244,962✔
422
{
423
    auto& sample = ensure_sample(elf, ypc.ypc_index);
244,962✔
424

425
    return &sample;
244,962✔
426
}
427

428
static int
429
read_json_constant(yajlpp_parse_context* ypc,
45,782✔
430
                   const unsigned char* str,
431
                   size_t len,
432
                   yajl_string_props_t*)
433
{
434
    auto val = std::string((const char*) str, len);
45,782✔
435
    auto elf = (external_log_format*) ypc->ypc_obj_stack.top();
45,782✔
436

437
    ypc->ypc_array_index.back() += 1;
45,782✔
438
    auto& jfe = ensure_json_format_element(elf, ypc->ypc_array_index.back());
45,782✔
439
    jfe.jfe_type = external_log_format::json_log_field::CONSTANT;
45,782✔
440
    jfe.jfe_default_value = val;
45,782✔
441

442
    return 1;
45,782✔
443
}
45,782✔
444

445
static const struct json_path_container pattern_handlers = {
446
    yajlpp::property_handler("pattern")
447
        .with_synopsis("<message-regex>")
448
        .with_description(
449
            "The regular expression to match a log message and capture fields.")
450
        .with_min_length(1)
451
        .for_field(&external_log_format::pattern::p_pcre),
452
};
453

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

459
    json_path_handler_base::ENUM_TERMINATOR,
460
};
461

462
static constexpr json_path_handler_base::enum_value_t TS_POR_ENUM[] = {
463
    {"end"_frag, timestamp_point_of_reference_t::end},
464
    {"start"_frag, timestamp_point_of_reference_t::start},
465

466
    json_path_handler_base::ENUM_TERMINATOR,
467
};
468

469
static constexpr json_path_handler_base::enum_value_t ALIGN_ENUM[] = {
470
    {"left"_frag, external_log_format::json_format_element::align_t::LEFT},
471
    {"right"_frag, external_log_format::json_format_element::align_t::RIGHT},
472

473
    json_path_handler_base::ENUM_TERMINATOR,
474
};
475

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

486
    json_path_handler_base::ENUM_TERMINATOR,
487
};
488

489
static constexpr json_path_handler_base::enum_value_t TRANSFORM_ENUM[] = {
490
    {"none"_frag, external_log_format::json_format_element::transform_t::NONE},
491
    {"uppercase"_frag,
492
     external_log_format::json_format_element::transform_t::UPPERCASE},
493
    {"lowercase"_frag,
494
     external_log_format::json_format_element::transform_t::LOWERCASE},
495
    {"capitalize"_frag,
496
     external_log_format::json_format_element::transform_t::CAPITALIZE},
497

498
    json_path_handler_base::ENUM_TERMINATOR,
499
};
500

501
static constexpr json_path_handler_base::enum_value_t OPID_SOURCE_ENUM[] = {
502
    {"from-description"_frag, log_format::opid_source_t::from_description},
503
    {"from-whole-msg"_frag, log_format::opid_source_t::from_whole_msg},
504

505
    json_path_handler_base::ENUM_TERMINATOR,
506
};
507

508
static const json_path_container line_format_handlers = {
509
    yajlpp::property_handler("field")
510
        .with_synopsis("<field-name>")
511
        .with_description(
512
            "The name of the field to substitute at this position")
513
        .for_field(&external_log_format::json_format_element::jfe_value),
514

515
    yajlpp::property_handler("default-value")
516
        .with_synopsis("<string>")
517
        .with_description(
518
            "The default value for this position if the field is null")
519
        .for_field(
520
            &external_log_format::json_format_element::jfe_default_value),
521

522
    yajlpp::property_handler("timestamp-format")
523
        .with_synopsis("<string>")
524
        .with_min_length(1)
525
        .with_description("The strftime(3) format for this field")
526
        .for_field(&external_log_format::json_format_element::jfe_ts_format),
527

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

534
    yajlpp::property_handler("auto-width")
535
        .with_description("Automatically detect the necessary width of the "
536
                          "field based on the observed values")
537
        .for_field(&external_log_format::json_format_element::jfe_auto_width),
538

539
    yajlpp::property_handler("max-width")
540
        .with_min_value(0)
541
        .with_synopsis("<size>")
542
        .with_description("The maximum width of the field")
543
        .for_field(&external_log_format::json_format_element::jfe_max_width),
544

545
    yajlpp::property_handler("align")
546
        .with_synopsis("left|right")
547
        .with_description(
548
            "Align the text in the column to the left or right side")
549
        .with_enum_values(ALIGN_ENUM)
550
        .for_field(&external_log_format::json_format_element::jfe_align),
551

552
    yajlpp::property_handler("overflow")
553
        .with_synopsis("abbrev|truncate|dot-dot")
554
        .with_description("Overflow style")
555
        .with_enum_values(OVERFLOW_ENUM)
556
        .for_field(&external_log_format::json_format_element::jfe_overflow),
557

558
    yajlpp::property_handler("text-transform")
559
        .with_synopsis("none|uppercase|lowercase|capitalize")
560
        .with_description("Text transformation")
561
        .with_enum_values(TRANSFORM_ENUM)
562
        .for_field(
563
            &external_log_format::json_format_element::jfe_text_transform),
564

565
    yajlpp::property_handler("prefix")
566
        .with_synopsis("<str>")
567
        .with_description("Text to prepend to the value")
568
        .for_field(&external_log_format::json_format_element::jfe_prefix),
569

570
    yajlpp::property_handler("suffix")
571
        .with_synopsis("<str>")
572
        .with_description("Text to append to the value")
573
        .for_field(&external_log_format::json_format_element::jfe_suffix),
574
};
575

576
static constexpr json_path_handler_base::enum_value_t KIND_ENUM[] = {
577
    {"string"_frag, value_kind_t::VALUE_TEXT},
578
    {"integer"_frag, value_kind_t::VALUE_INTEGER},
579
    {"float"_frag, value_kind_t::VALUE_FLOAT},
580
    {"boolean"_frag, value_kind_t::VALUE_BOOLEAN},
581
    {"json"_frag, value_kind_t::VALUE_JSON},
582
    {"struct"_frag, value_kind_t::VALUE_STRUCT},
583
    {"quoted"_frag, value_kind_t::VALUE_QUOTED},
584
    {"xml"_frag, value_kind_t::VALUE_XML},
585
    {"timestamp"_frag, value_kind_t::VALUE_TIMESTAMP},
586
    {"any"_frag, value_kind_t::VALUE_ANY},
587

588
    json_path_handler_base::ENUM_TERMINATOR,
589
};
590

591
static constexpr json_path_handler_base::enum_value_t SCALE_OP_ENUM[] = {
592
    {"identity"_frag, scale_op_t::SO_IDENTITY},
593
    {"multiply"_frag, scale_op_t::SO_MULTIPLY},
594
    {"divide"_frag, scale_op_t::SO_DIVIDE},
595

596
    json_path_handler_base::ENUM_TERMINATOR,
597
};
598

599
static const struct json_path_container scaling_factor_handlers = {
600
    yajlpp::property_handler("op")
601
        .with_enum_values(SCALE_OP_ENUM)
602
        .for_field(&scaling_factor::sf_op),
603

604
    yajlpp::property_handler("value").for_field(&scaling_factor::sf_value),
605
};
606

607
static const struct json_path_container scale_handlers = {
608
    yajlpp::pattern_property_handler("(?<scale>[^/]+)")
609
        .with_obj_provider(scaling_factor_provider)
610
        .with_children(scaling_factor_handlers),
611
};
612

613
static const struct json_path_container unit_handlers = {
614
    yajlpp::property_handler("field")
615
        .with_synopsis("<field-name>")
616
        .with_description(
617
            "The name of the field that contains the units for this field")
618
        .for_field(&external_log_format::value_def::vd_unit_field),
619

620
    yajlpp::property_handler("scaling-factor")
621
        .with_description("Transforms the numeric value by the given factor")
622
        .with_children(scale_handlers),
623
};
624

625
static const json_path_container capture_highlight_handlers = {
626
    yajlpp::pattern_property_handler(R"((?<hl_cap_name>[^/]+))")
627
        .with_description("The definition of a capture highlight")
628
        .with_obj_provider<style_config,
629
                           std::map<intern_string_t, style_config>>(
630
            [](const yajlpp_provider_context& ypc,
306✔
631
               std::map<intern_string_t, style_config>* root) {
632
                auto* retval = &(*root)[ypc.get_substr_i(0)];
306✔
633

634
                return retval;
306✔
635
            })
636
        .with_children(style_config_handlers),
637
};
638

639
static const json_path_container highlighter_def_handlers = {
640
    yajlpp::property_handler("pattern")
641
        .with_synopsis("<regex>")
642
        .with_description(
643
            "A regular expression to highlight in logs of this format.")
644
        .for_field(&external_log_format::highlighter_def::hd_pattern),
645

646
    yajlpp::property_handler("base-style")
647
        .with_description("The style to use for the entire pattern")
648
        .for_child(&external_log_format::highlighter_def::hd_base_style)
649
        .with_children(style_config_handlers),
650

651
    yajlpp::property_handler("captures")
652
        .with_description("Styles for individual named capture groups in the pattern")
653
        .for_child(&external_log_format::highlighter_def::hd_capture_styles)
654
        .with_children(capture_highlight_handlers),
655
};
656

657
static const json_path_container legacy_highlight_handlers = {
658
    yajlpp::property_handler("pattern")
659
        .with_synopsis("<regex>")
660
        .with_description(
661
            "A regular expression to highlight in logs of this format.")
662
        .for_field(&external_log_format::highlighter_def::hd_pattern),
663
    yajlpp::property_handler("color")
664
        .with_synopsis("#<hex>|<name>")
665
        .with_description("The color to use when highlighting this pattern.")
666
        .for_field(&external_log_format::highlighter_def::hd_base_style,
667
                   &style_config::sc_color),
668
    yajlpp::property_handler("background-color")
669
        .with_synopsis("#<hex>|<name>")
670
        .with_description(
671
            "The background color to use when highlighting this pattern.")
672
        .for_field(&external_log_format::highlighter_def::hd_base_style,
673
                   &style_config::sc_background_color),
674
    yajlpp::property_handler("underline")
675
        .with_synopsis("<enabled>")
676
        .with_description("Highlight this pattern with an underline.")
677
        .for_field(&external_log_format::highlighter_def::hd_base_style,
678
                   &style_config::sc_underline),
679
    yajlpp::property_handler("blink")
680
        .with_synopsis("<enabled>")
681
        .with_description("Highlight this pattern by blinking.")
682
        .for_field(&external_log_format::highlighter_def::hd_base_style,
683
                   &style_config::sc_blink),
684
    yajlpp::property_handler("nestable")
685
        .with_synopsis("<enabled>")
686
        .with_description("This highlight can be nested in another highlight.")
687
        .for_field(&external_log_format::highlighter_def::hd_base_style,
688
                   &style_config::sc_nestable),
689
};
690

691
static const json_path_container legacy_highlight_def_handlers = {
692
    yajlpp::pattern_property_handler(R"((?<highlight_name>[^/]+))")
693
        .with_description("The definition of a highlight")
694
        .with_obj_provider<external_log_format::highlighter_def,
695
                           external_log_format>(
696
            [](const yajlpp_provider_context& ypc, external_log_format* root) {
5,057✔
697
                auto* retval
698
                    = &(root->elf_highlighter_patterns[ypc.get_substr_i(0)]);
5,057✔
699

700
                return retval;
5,057✔
701
            })
702
        .with_children(legacy_highlight_handlers),
703
};
704

705
static const json_path_container value_highlight_handlers = {
706
    yajlpp::pattern_property_handler(R"((?<highlight_name>[^/]+))")
707
        .with_description("The definition of a highlight")
708
        .with_obj_provider<external_log_format::highlighter_def,
709
                           external_log_format::value_def>(
710
            [](const yajlpp_provider_context& ypc,
26,604✔
711
               external_log_format::value_def* root) {
712
                auto* retval
713
                    = &(root->vd_highlighter_patterns[ypc.get_substr_i(0)]);
26,604✔
714

715
                return retval;
26,604✔
716
            })
717
        .with_children(highlighter_def_handlers),
718
};
719

720
static const struct json_path_container value_def_handlers = {
721
    yajlpp::property_handler("kind")
722
        .with_synopsis("<data-type>")
723
        .with_description("The type of data in the field")
724
        .with_enum_values(KIND_ENUM)
725
        .for_field(&external_log_format::value_def::vd_meta,
726
                   &logline_value_meta::lvm_kind),
727

728
    yajlpp::property_handler("collate")
729
        .with_synopsis("<function>")
730
        .with_description("The collating function to use for this column")
731
        .for_field(&external_log_format::value_def::vd_collate),
732

733
    yajlpp::property_handler("unit")
734
        .with_description("Unit definitions for this field")
735
        .with_children(unit_handlers),
736

737
    yajlpp::property_handler("identifier")
738
        .with_synopsis("<bool>")
739
        .with_description("Indicates whether or not this field contains an "
740
                          "identifier that should be highlighted")
741
        .for_field(&external_log_format::value_def::vd_meta,
742
                   &logline_value_meta::lvm_identifier),
743

744
    yajlpp::property_handler("foreign-key")
745
        .with_synopsis("<bool>")
746
        .with_description("Indicates whether or not this field should be "
747
                          "treated as a foreign key for row in another table")
748
        .for_field(&external_log_format::value_def::vd_meta,
749
                   &logline_value_meta::lvm_foreign_key),
750

751
    yajlpp::property_handler("hidden")
752
        .with_synopsis("<bool>")
753
        .with_description(
754
            "Indicates whether or not this field should be hidden")
755
        .for_field(&external_log_format::value_def::vd_meta,
756
                   &logline_value_meta::lvm_hidden),
757

758
    yajlpp::property_handler("action-list#")
759
        .with_synopsis("<string>")
760
        .with_description("Actions to execute when this field is clicked on")
761
        .for_field(&external_log_format::value_def::vd_action_list),
762

763
    yajlpp::property_handler("rewriter")
764
        .with_synopsis("<command>")
765
        .with_description(
766
            "A command that will rewrite this field when pretty-printing")
767
        .for_field(&external_log_format::value_def::vd_rewriter)
768
        .with_example(
769
            ";SELECT :sc_status || ' (' || (SELECT message FROM "
770
            "http_status_codes WHERE status = :sc_status) || ') '"_frag),
771

772
    yajlpp::property_handler("description")
773
        .with_synopsis("<string>")
774
        .with_description("A description of the field")
775
        .for_field(&external_log_format::value_def::vd_description),
776

777
    yajlpp::property_handler("highlights")
778
        .with_description("The set of highlight definitions")
779
        .with_children(value_highlight_handlers),
780
};
781

782
static const struct json_path_container sample_handlers = {
783
    yajlpp::property_handler("description")
784
        .with_synopsis("<text>")
785
        .with_description("A description of this sample.")
786
        .for_field(&external_log_format::sample_t::s_description),
787
    yajlpp::property_handler("line")
788
        .with_synopsis("<log-line>")
789
        .with_description(
790
            "A sample log line that should match a pattern in this format.")
791
        .for_field(&external_log_format::sample_t::s_line),
792

793
    yajlpp::property_handler("level")
794
        .with_enum_values(LEVEL_ENUM)
795
        .with_description("The expected level for this sample log line.")
796
        .for_field(&external_log_format::sample_t::s_level),
797
};
798

799
static constexpr json_path_handler_base::enum_value_t TYPE_ENUM[] = {
800
    {"text"_frag, external_log_format::elf_type_t::ELF_TYPE_TEXT},
801
    {"json"_frag, external_log_format::elf_type_t::ELF_TYPE_JSON},
802
    {"csv"_frag, external_log_format::elf_type_t::ELF_TYPE_CSV},
803

804
    json_path_handler_base::ENUM_TERMINATOR,
805
};
806

807
static const struct json_path_container regex_handlers = {
808
    yajlpp::pattern_property_handler(R"((?<pattern_name>[^/]+))")
809
        .with_description("The set of patterns used to match log messages")
810
        .with_obj_provider(pattern_provider)
811
        .with_children(pattern_handlers),
812
};
813

814
static const struct json_path_container level_handlers = {
815
    yajlpp::pattern_property_handler("(?<level>trace|debug[2345]?|info|stats|"
816
                                     "notice|warning|error|critical|fatal)")
817
        .add_cb(read_levels)
818
        .add_cb(read_level_int)
819
        .with_synopsis("<pattern|integer>")
820
        .with_description("The regular expression used to match the log text "
821
                          "for this level.  "
822
                          "For JSON logs with numeric levels, this should be "
823
                          "the number for the corresponding level."),
824
};
825

826
static const struct json_path_container value_handlers = {
827
    yajlpp::pattern_property_handler("(?<value_name>[^/]+)")
828
        .with_description(
829
            "The set of values captured by the log message patterns")
830
        .with_obj_provider(value_def_provider)
831
        .with_children(value_def_handlers),
832
};
833

834
static const struct json_path_container tag_path_handlers = {
835
    yajlpp::property_handler("glob")
836
        .with_synopsis("<glob>")
837
        .with_description("The glob to match against file paths")
838
        .with_example("*/system.log*"_frag)
839
        .for_field(&format_tag_def::path_restriction::p_glob),
840
};
841

842
static const struct json_path_container format_tag_def_handlers = {
843
    yajlpp::property_handler("paths#")
844
        .with_description("Restrict tagging to the given paths")
845
        .for_field(&format_tag_def::ftd_paths)
846
        .with_children(tag_path_handlers),
847
    yajlpp::property_handler("pattern")
848
        .with_synopsis("<regex>")
849
        .with_description("The regular expression to match against the body of "
850
                          "the log message")
851
        .with_example("\\w+ is down"_frag)
852
        .for_field(&format_tag_def::ftd_pattern),
853
    yajlpp::property_handler("description")
854
        .with_synopsis("<string>")
855
        .with_description("A description of this tag")
856
        .for_field(&format_tag_def::ftd_description),
857
    json_path_handler("level")
858
        .with_synopsis("<log-level>")
859
        .with_description("Constrain hits to log messages with this level")
860
        .with_enum_values(LEVEL_ENUM)
861
        .for_field(&format_tag_def::ftd_level),
862
};
863

864
static const struct json_path_container tag_handlers = {
865
    yajlpp::pattern_property_handler(R"((?<tag_name>[\w:;\._\-]+))")
866
        .with_description("The name of the tag to apply")
867
        .with_obj_provider(format_tag_def_provider)
868
        .with_children(format_tag_def_handlers),
869
};
870

871
static const struct json_path_container format_partition_def_handlers = {
872
    yajlpp::property_handler("paths#")
873
        .with_description("Restrict partitioning to the given paths")
874
        .for_field(&format_partition_def::fpd_paths)
875
        .with_children(tag_path_handlers),
876
    yajlpp::property_handler("pattern")
877
        .with_synopsis("<regex>")
878
        .with_description("The regular expression to match against the body of "
879
                          "the log message")
880
        .with_example("\\w+ is down"_frag)
881
        .for_field(&format_partition_def::fpd_pattern),
882
    yajlpp::property_handler("description")
883
        .with_synopsis("<string>")
884
        .with_description("A description of this partition")
885
        .for_field(&format_partition_def::fpd_description),
886
    json_path_handler("level")
887
        .with_synopsis("<log-level>")
888
        .with_description("Constrain hits to log messages with this level")
889
        .with_enum_values(LEVEL_ENUM)
890
        .for_field(&format_partition_def::fpd_level),
891
};
892

893
static const struct json_path_container partition_handlers = {
894
    yajlpp::pattern_property_handler(R"((?<partition_type>[\w:;\._\- ]+))")
895
        .with_description("The type of partition to apply")
896
        .with_obj_provider(format_partition_def_provider)
897
        .with_children(format_partition_def_handlers),
898
};
899

900
static const struct json_path_container action_def_handlers = {
901
    json_path_handler("label", read_action_def),
902
    json_path_handler("capture-output", read_action_bool),
903
    json_path_handler("cmd#", read_action_cmd),
904
};
905

906
static const struct json_path_container action_handlers = {
907
    json_path_handler(
908
        lnav::pcre2pp::code::from_const("(?<action_name>\\w+)").to_shared(),
909
        read_action_def)
910
        .with_children(action_def_handlers),
911
};
912

913
static const struct json_path_container search_table_def_handlers = {
914
    json_path_handler("pattern")
915
        .with_synopsis("<regex>")
916
        .with_description("The regular expression for this search table.")
917
        .for_field(&external_log_format::search_table_def::std_pattern),
918
    json_path_handler("glob")
919
        .with_synopsis("<glob>")
920
        .with_description("Glob pattern used to constrain hits to messages "
921
                          "that match the given pattern.")
922
        .for_field(&external_log_format::search_table_def::std_glob),
923
    json_path_handler("level")
924
        .with_synopsis("<log-level>")
925
        .with_description("Constrain hits to log messages with this level")
926
        .with_enum_values(LEVEL_ENUM)
927
        .for_field(&external_log_format::search_table_def::std_level),
928
};
929

930
static const struct json_path_container search_table_handlers = {
931
    yajlpp::pattern_property_handler("(?<table_name>\\w+)")
932
        .with_description(
933
            "The set of search tables to be automatically defined")
934
        .with_obj_provider<external_log_format::search_table_def,
935
                           external_log_format>(
936
            [](const yajlpp_provider_context& ypc, external_log_format* root) {
41,453✔
937
                auto* retval = &(root->elf_search_tables[ypc.get_substr_i(0)]);
41,453✔
938

939
                return retval;
41,453✔
940
            })
941
        .with_children(search_table_def_handlers),
942
};
943

944
static const struct json_path_container header_expr_handlers = {
945
    yajlpp::pattern_property_handler(R"((?<header_expr_name>\w+))")
946
        .with_description("SQLite expression")
947
        .for_field(&external_log_format::header_exprs::he_exprs),
948
};
949

950
static const struct json_path_container header_handlers = {
951
    yajlpp::property_handler("expr")
952
        .with_description("The expressions used to check if a file header "
953
                          "matches this file format")
954
        .for_child(&external_log_format::header::h_exprs)
955
        .with_children(header_expr_handlers),
956
    yajlpp::property_handler("size")
957
        .with_description("The minimum size required for this header type")
958
        .for_field(&external_log_format::header::h_size),
959
};
960

961
static const struct json_path_container converter_handlers = {
962
    yajlpp::property_handler("type")
963
        .with_description("The MIME type")
964
        .for_field(&external_log_format::converter::c_type),
965
    yajlpp::property_handler("header")
966
        .with_description("File header detection definitions")
967
        .for_child(&external_log_format::converter::c_header)
968
        .with_children(header_handlers),
969
    yajlpp::property_handler("command")
970
        .with_description("The script used to convert the file")
971
        .with_pattern(R"([\w\.\-]+)")
972
        .for_field(&external_log_format::converter::c_command),
973
};
974

975
static const struct json_path_container opid_descriptor_handlers = {
976
    yajlpp::property_handler("field")
977
        .with_synopsis("<name>")
978
        .with_description("The field to include in the operation description")
979
        .for_field(&log_format::opid_descriptor::od_field),
980
    yajlpp::property_handler("extractor")
981
        .with_synopsis("<regex>")
982
        .with_description(
983
            "The regex used to extract content for the operation description")
984
        .for_field(&log_format::opid_descriptor::od_extractor),
985
    yajlpp::property_handler("prefix")
986
        .with_description(
987
            "A string to prepend to this field in the description")
988
        .for_field(&log_format::opid_descriptor::od_prefix),
989
    yajlpp::property_handler("suffix")
990
        .with_description("A string to append to this field in the description")
991
        .for_field(&log_format::opid_descriptor::od_suffix),
992
    yajlpp::property_handler("joiner")
993
        .with_description("A string to insert between instances of this field "
994
                          "when the field is found more than once")
995
        .for_field(&log_format::opid_descriptor::od_joiner),
996
};
997

998
static const struct json_path_container opid_description_format_handlers = {
999
    yajlpp::property_handler("format#")
1000
        .with_description("Defines the elements of this operation description")
1001
        .for_field(&log_format::opid_descriptors::od_descriptors)
1002
        .with_children(opid_descriptor_handlers),
1003
};
1004

1005
static const struct json_path_container opid_description_handlers = {
1006
    yajlpp::pattern_property_handler(R"((?<opid_descriptor>[\w\.\-]+))")
1007
        .with_description("A type of description for this operation")
1008
        .for_field(&log_format::lf_opid_description_def)
1009
        .with_children(opid_description_format_handlers),
1010
};
1011

1012
static const struct json_path_container subid_description_handlers = {
1013
    yajlpp::pattern_property_handler(R"((?<subid_descriptor>[\w\.\-]+))")
1014
        .with_description("A type of description for this sub-operation")
1015
        .for_field(&log_format::lf_subid_description_def)
1016
        .with_children(opid_description_format_handlers),
1017
};
1018

1019
static const struct json_path_container opid_handlers = {
1020
    yajlpp::property_handler("source")
1021
        .with_description("The source of the operation ID")
1022
        .with_enum_values(OPID_SOURCE_ENUM)
1023
        .for_field(&log_format::lf_opid_source),
1024
    yajlpp::property_handler("subid")
1025
        .with_description("The field that holds the ID for a sub-operation")
1026
        .for_field(&external_log_format::elf_subid_field),
1027
    yajlpp::property_handler("description")
1028
        .with_description(
1029
            "Define how to construct a description of an operation")
1030
        .with_children(opid_description_handlers),
1031
    yajlpp::property_handler("sub-description")
1032
        .with_description(
1033
            "Define how to construct a description of a sub-operation")
1034
        .with_children(subid_description_handlers),
1035
};
1036

1037
const struct json_path_container format_handlers = {
1038
    yajlpp::property_handler("regex")
1039
        .with_description(
1040
            "The set of regular expressions used to match log messages")
1041
        .with_children(regex_handlers),
1042

1043
    json_path_handler("json", read_format_bool)
1044
        .with_description(
1045
            R"(Indicates that log files are JSON-encoded (deprecated, use "file-type": "json"))"),
1046
    json_path_handler("convert-to-local-time")
1047
        .with_description("Indicates that displayed timestamps should "
1048
                          "automatically be converted to local time")
1049
        .for_field(&external_log_format::lf_date_time,
1050
                   &date_time_scanner::dts_local_time),
1051
    json_path_handler("hide-extra")
1052
        .with_description("If 'true', JSON-log values that are not defined in "
1053
                          "the 'value' object are hidden")
1054
        .for_field(&external_log_format::jlf_hide_extra),
1055
    json_path_handler("multiline")
1056
        .with_description("Indicates that log messages can span multiple lines")
1057
        .for_field(&log_format::lf_multiline),
1058
    json_path_handler("timestamp-divisor", read_format_double)
1059
        .add_cb(read_format_int)
1060
        .with_synopsis("<number>")
1061
        .with_description(
1062
            "The value to divide a numeric timestamp by in a JSON log."),
1063
    json_path_handler("file-pattern")
1064
        .with_description("A regular expression that restricts this format to "
1065
                          "log files with a matching name")
1066
        .for_field(&external_log_format::elf_filename_pcre),
1067
    json_path_handler("converter")
1068
        .with_description("Describes how the file format can be detected and "
1069
                          "converted to a log that can be understood by lnav")
1070
        .for_child(&external_log_format::elf_converter)
1071
        .with_children(converter_handlers),
1072
    json_path_handler("level-field")
1073
        .with_description(
1074
            "The name of the level field in the log message pattern")
1075
        .for_field(&external_log_format::elf_level_field),
1076
    json_path_handler("level-pointer")
1077
        .with_description("A regular-expression that matches the JSON-pointer "
1078
                          "of the level property")
1079
        .for_field(&external_log_format::elf_level_pointer),
1080
    json_path_handler("timestamp-field")
1081
        .with_description(
1082
            "The name of the timestamp field in the log message pattern")
1083
        .for_field(&log_format::lf_timestamp_field),
1084
    json_path_handler("start-timestamp-field")
1085
        .with_description(
1086
            "The name of the field that contains the start time of the "
1087
            "operation.  The timestamp-field is treated as the end time "
1088
            "and the duration is computed as the difference.")
1089
        .for_field(&log_format::lf_start_timestamp_field),
1090
    json_path_handler("subsecond-field")
1091
        .with_description("The path to the property in a JSON-lines log "
1092
                          "message that contains the sub-second time value")
1093
        .for_field(&log_format::lf_subsecond_field),
1094
    json_path_handler("subsecond-units")
1095
        .with_description("The units of the subsecond-field property value")
1096
        .with_enum_values(SUBSECOND_UNIT_ENUM)
1097
        .for_field(&log_format::lf_subsecond_unit),
1098
    json_path_handler("time-field")
1099
        .with_description(
1100
            "The name of the time field in the log message pattern.  This "
1101
            "field should only be specified if the timestamp field only "
1102
            "contains a date.")
1103
        .for_field(&log_format::lf_time_field),
1104
    json_path_handler("timestamp-point-of-reference")
1105
        .with_description("The relation of the timestamp to the operation that "
1106
                          "the message refers to.")
1107
        .with_enum_values(TS_POR_ENUM)
1108
        .for_field(&external_log_format::lf_timestamp_point_of_reference),
1109
    json_path_handler("body-field")
1110
        .with_description(
1111
            "The name of the body field in the log message pattern")
1112
        .for_field(&external_log_format::elf_body_field),
1113
    json_path_handler("thread-id-field")
1114
        .with_description(
1115
            "The name of the thread ID field in the log message pattern")
1116
        .for_field(&external_log_format::elf_thread_id_field),
1117
    json_path_handler("src-file-field")
1118
        .with_description(
1119
            "The name of the source file field in the log message pattern")
1120
        .for_field(&external_log_format::elf_src_file_field),
1121
    json_path_handler("src-line-field")
1122
        .with_description(
1123
            "The name of the source line field in the log message pattern")
1124
        .for_field(&external_log_format::elf_src_line_field),
1125
    json_path_handler("src-location-field")
1126
        .with_description("The name of the field that contains the source file "
1127
                          "and line number")
1128
        .for_field(&external_log_format::elf_src_loc_field),
1129
    json_path_handler("duration-field")
1130
        .with_description(
1131
            "The name of the duration field in the log message pattern")
1132
        .for_field(&external_log_format::elf_duration_field),
1133
    json_path_handler("duration-divisor", read_format_double)
1134
        .add_cb(read_format_int)
1135
        .with_synopsis("<number>")
1136
        .with_description(
1137
            "The value to divide a duration by to convert it to "
1138
            "seconds.  For example, if the duration field is in "
1139
            "milliseconds, the divisor should be 1000."),
1140
    json_path_handler("url",
1141
                      lnav::pcre2pp::code::from_const("^url#?").to_shared())
1142
        .add_cb(read_format_field)
1143
        .with_description("A URL with more information about this log format"),
1144
    json_path_handler("title", read_format_field)
1145
        .with_description("The human-readable name for this log format"),
1146
    json_path_handler("description")
1147
        .with_description("A longer description of this log format")
1148
        .for_field(&external_log_format::lf_description),
1149
    json_path_handler("timestamp-format#", read_format_field)
1150
        .with_description("An array of strptime(3)-like timestamp formats"),
1151
    json_path_handler("opid-field")
1152
        .with_description(
1153
            "The name of the operation-id field in the log message pattern")
1154
        .for_field(&external_log_format::elf_opid_field),
1155
    yajlpp::property_handler("opid")
1156
        .with_description("Definitions related to operations found in logs")
1157
        .with_children(opid_handlers),
1158
    yajlpp::property_handler("ordered-by-time")
1159
        .with_synopsis("<bool>")
1160
        .with_description(
1161
            "Indicates that the order of messages in the file is time-based.")
1162
        .for_field(&log_format::lf_time_ordered),
1163
    yajlpp::property_handler("level")
1164
        .with_description(
1165
            "The map of level names to patterns or integer values")
1166
        .with_children(level_handlers),
1167

1168
    yajlpp::property_handler("value")
1169
        .with_description("The set of value definitions")
1170
        .with_children(value_handlers),
1171

1172
    yajlpp::property_handler("tags")
1173
        .with_description("The tags to automatically apply to log messages")
1174
        .with_children(tag_handlers),
1175

1176
    yajlpp::property_handler("partitions")
1177
        .with_description(
1178
            "The partitions to automatically apply to log messages")
1179
        .with_children(partition_handlers),
1180

1181
    yajlpp::property_handler("action").with_children(action_handlers),
1182
    yajlpp::property_handler("sample#")
1183
        .with_description("An array of sample log messages to be tested "
1184
                          "against the log message patterns")
1185
        .with_obj_provider(sample_provider)
1186
        .with_children(sample_handlers),
1187

1188
    yajlpp::property_handler("line-format#")
1189
        .with_description("The display format for JSON-encoded log messages")
1190
        .with_obj_provider(line_format_provider)
1191
        .add_cb(read_json_constant)
1192
        .with_children(line_format_handlers),
1193
    json_path_handler("search-table")
1194
        .with_description(
1195
            "Search tables to automatically define for this log format")
1196
        .with_children(search_table_handlers),
1197

1198
    yajlpp::property_handler("highlights")
1199
        .with_description("The set of highlight definitions")
1200
        .with_children(legacy_highlight_def_handlers),
1201

1202
    yajlpp::property_handler("file-type")
1203
        .with_synopsis("text|json|csv")
1204
        .with_description("The type of file that contains the log messages")
1205
        .with_enum_values(TYPE_ENUM)
1206
        .for_field(&external_log_format::elf_type),
1207

1208
    yajlpp::property_handler("max-unrecognized-lines")
1209
        .with_synopsis("<lines>")
1210
        .with_description("The maximum number of lines in a file to use when "
1211
                          "detecting the format")
1212
        .with_min_value(1)
1213
        .for_field(&log_format::lf_max_unrecognized_lines),
1214
};
1215

1216
static int
1217
read_id(yajlpp_parse_context* ypc,
60,925✔
1218
        const unsigned char* str,
1219
        size_t len,
1220
        yajl_string_props_t*)
1221
{
1222
    auto* ud = static_cast<loader_userdata*>(ypc->ypc_userdata);
60,925✔
1223
    auto file_id = std::string((const char*) str, len);
60,925✔
1224

1225
    ud->ud_file_schema = file_id;
60,925✔
1226
    if (SUPPORTED_FORMAT_SCHEMAS.find(file_id)
60,925✔
1227
        == SUPPORTED_FORMAT_SCHEMAS.end())
121,850✔
1228
    {
1229
        const auto* handler = ypc->ypc_current_handler;
1✔
1230
        attr_line_t notes{"expecting one of the following $schema values:"};
1✔
1231

1232
        for (const auto& schema : SUPPORTED_FORMAT_SCHEMAS) {
2✔
1233
            notes.append("\n").append(
2✔
1234
                lnav::roles::symbol(fmt::format(FMT_STRING("  {}"), schema)));
5✔
1235
        }
1236
        ypc->report_error(
2✔
1237
            lnav::console::user_message::error(
×
1238
                attr_line_t("'")
1✔
1239
                    .append(lnav::roles::symbol(file_id))
2✔
1240
                    .append("' is not a supported log format $schema version"))
1✔
1241
                .with_snippet(ypc->get_snippet())
2✔
1242
                .with_note(notes)
1✔
1243
                .with_help(handler->get_help_text(ypc)));
1✔
1244
    }
1✔
1245

1246
    return 1;
60,925✔
1247
}
60,925✔
1248

1249
const struct json_path_container root_format_handler = json_path_container{
1250
    json_path_handler("$schema", read_id)
1251
        .with_synopsis("The URI of the schema for this file")
1252
        .with_description("Specifies the type of this file"),
1253

1254
    yajlpp::pattern_property_handler("(?<format_name>\\w+)")
1255
        .with_description("The definition of a log file format.")
1256
        .with_obj_provider(ensure_format)
1257
        .with_children(format_handlers),
1258
}
1259
    .with_schema_id(DEFAULT_FORMAT_SCHEMA);
1260

1261
static void
1262
write_sample_file()
829✔
1263
{
1264
    const auto dstdir = lnav::paths::dotlnav();
829✔
1265
    for (const auto& bsf : lnav_format_json) {
60,517✔
1266
        auto sample_path = dstdir
1267
            / fmt::format(FMT_STRING("formats/default/{}.sample"),
238,752✔
1268
                          bsf.get_name());
119,376✔
1269

1270
        const auto& name_sf = bsf.get_name();
59,688✔
1271
        auto stat_res = lnav::filesystem::stat_file(sample_path);
59,688✔
1272
        if (stat_res.isOk()) {
59,688✔
1273
            auto st = stat_res.unwrap();
51,552✔
1274
            if (st.st_mtime >= lnav::filesystem::self_mtime()) {
51,552✔
1275
                log_debug("skipping writing sample: %.*s (mtimes %ld >= %lld)",
51,552✔
1276
                          name_sf.length(),
1277
                          name_sf.data(),
1278
                          st.st_mtime,
1279
                          lnav::filesystem::self_mtime());
1280
                continue;
51,552✔
1281
            }
1282
            log_debug("sample file needs to be updated: %.*s",
×
1283
                      name_sf.length(),
1284
                      name_sf.data());
1285
        } else {
1286
            log_debug("sample file does not exist: %.*s",
8,136✔
1287
                      name_sf.length(),
1288
                      name_sf.data());
1289
        }
1290

1291
        auto sfp = bsf.to_string_fragment_producer();
8,136✔
1292
        auto write_res = lnav::filesystem::write_file(
1293
            sample_path,
1294
            *sfp,
8,136✔
1295
            {lnav::filesystem::write_file_options::read_only});
16,272✔
1296

1297
        if (write_res.isErr()) {
8,136✔
1298
            auto msg = write_res.unwrapErr();
4,896✔
1299
            fprintf(stderr,
4,896✔
1300
                    "error:unable to write default format file: %s -- %s\n",
1301
                    sample_path.c_str(),
1302
                    msg.c_str());
1303
        }
4,896✔
1304
    }
111,240✔
1305

1306
    for (const auto& bsf : lnav_sh_scripts) {
4,145✔
1307
        auto sh_path = dstdir
1308
            / fmt::format(FMT_STRING("formats/default/{}"), bsf.get_name());
13,264✔
1309
        auto stat_res = lnav::filesystem::stat_file(sh_path);
3,316✔
1310
        if (stat_res.isOk()) {
3,316✔
1311
            auto st = stat_res.unwrap();
2,864✔
1312
            if (st.st_mtime >= lnav::filesystem::self_mtime()) {
2,864✔
1313
                continue;
2,864✔
1314
            }
1315
        }
1316

1317
        auto sfp = bsf.to_string_fragment_producer();
452✔
1318
        auto write_res = lnav::filesystem::write_file(
1319
            sh_path,
1320
            *sfp,
452✔
1321
            {
1322
                lnav::filesystem::write_file_options::executable,
1323
                lnav::filesystem::write_file_options::read_only,
1324
            });
904✔
1325

1326
        if (write_res.isErr()) {
452✔
1327
            auto msg = write_res.unwrapErr();
272✔
1328
            fprintf(stderr,
272✔
1329
                    "error:unable to write default text file: %s -- %s\n",
1330
                    sh_path.c_str(),
1331
                    msg.c_str());
1332
        }
272✔
1333
    }
6,180✔
1334

1335
    for (const auto& bsf : lnav_scripts) {
18,238✔
1336
        script_metadata meta;
17,409✔
1337
        auto sf = bsf.to_string_fragment_producer()->to_string();
17,409✔
1338

1339
        meta.sm_name = bsf.get_name().to_string();
17,409✔
1340
        extract_metadata(sf, meta);
17,409✔
1341
        auto path
1342
            = fmt::format(FMT_STRING("formats/default/{}.lnav"), meta.sm_name);
52,227✔
1343
        auto script_path = dstdir / path;
17,409✔
1344
        auto stat_res = lnav::filesystem::stat_file(script_path);
17,409✔
1345
        if (stat_res.isOk()) {
17,409✔
1346
            auto st = stat_res.unwrap();
15,081✔
1347
            if (st.st_mtime >= lnav::filesystem::self_mtime()) {
15,081✔
1348
                continue;
15,081✔
1349
            }
1350
        }
1351

1352
        auto write_res = lnav::filesystem::write_file(
1353
            script_path,
1354
            sf,
1355
            {
1356
                lnav::filesystem::write_file_options::executable,
1357
                lnav::filesystem::write_file_options::read_only,
1358
            });
2,328✔
1359
        if (write_res.isErr()) {
2,328✔
1360
            fprintf(stderr,
1,428✔
1361
                    "error:unable to write default script file: %s -- %s\n",
1362
                    script_path.c_str(),
1363
                    strerror(errno));
1,428✔
1364
        }
1365
    }
77,733✔
1366
}
829✔
1367

1368
static void
1369
format_error_reporter(const yajlpp_parse_context& ypc,
16✔
1370
                      const lnav::console::user_message& msg)
1371
{
1372
    auto* ud = (loader_userdata*) ypc.ypc_userdata;
16✔
1373

1374
    ud->ud_errors->emplace_back(msg);
16✔
1375
}
16✔
1376

1377
std::vector<intern_string_t>
1378
load_format_file(const std::filesystem::path& filename,
1,239✔
1379
                 std::vector<lnav::console::user_message>& errors)
1380
{
1381
    std::vector<intern_string_t> retval;
1,239✔
1382
    loader_userdata ud;
1,239✔
1383
    auto_fd fd;
1,239✔
1384

1385
    log_info("loading formats from file: %s", filename.c_str());
1,239✔
1386
    yajlpp_parse_context ypc(intern_string::lookup(filename.string()),
2,478✔
1387
                             &root_format_handler);
1,239✔
1388
    ud.ud_parse_context = &ypc;
1,239✔
1389
    ud.ud_format_path = filename;
1,239✔
1390
    ud.ud_format_names = &retval;
1,239✔
1391
    ud.ud_errors = &errors;
1,239✔
1392
    ypc.ypc_userdata = &ud;
1,239✔
1393
    ypc.with_obj(ud);
1,239✔
1394
    if ((fd = lnav::filesystem::openp(filename, O_RDONLY)) == -1) {
1,239✔
1395
        errors.emplace_back(
×
1396
            lnav::console::user_message::error(
×
1397
                attr_line_t("unable to open format file: ")
×
1398
                    .append(lnav::roles::file(filename.string())))
×
1399
                .with_errno_reason());
1400
    } else {
1401
        auto_mem<yajl_handle_t> handle(yajl_free);
1,239✔
1402
        char buffer[2048];
1403
        off_t offset = 0;
1,239✔
1404
        ssize_t rc = -1;
1,239✔
1405

1406
        handle = yajl_alloc(&ypc.ypc_callbacks, nullptr, &ypc);
1,239✔
1407
        ypc.with_handle(handle).with_error_reporter(format_error_reporter);
1,239✔
1408
        yajl_config(handle, yajl_allow_comments, 1);
1,239✔
1409
        while (true) {
1410
            rc = read(fd, buffer, sizeof(buffer));
2,681✔
1411
            if (rc == 0) {
2,681✔
1412
                break;
1,238✔
1413
            }
1414
            if (rc == -1) {
1,443✔
1415
                errors.emplace_back(
×
1416
                    lnav::console::user_message::error(
×
1417
                        attr_line_t("unable to read format file: ")
×
1418
                            .append(lnav::roles::file(filename.string())))
×
1419
                        .with_errno_reason());
1420
                break;
×
1421
            }
1422
            if (offset == 0 && (rc > 2) && (buffer[0] == '#')
1,443✔
1423
                && (buffer[1] == '!'))
×
1424
            {
1425
                // Turn it into a JavaScript comment.
1426
                buffer[0] = buffer[1] = '/';
×
1427
            }
1428
            if (ypc.parse((const unsigned char*) buffer, rc) != yajl_status_ok)
1,443✔
1429
            {
1430
                break;
1✔
1431
            }
1432
            offset += rc;
1,442✔
1433
        }
1434
        if (rc == 0) {
1,239✔
1435
            ypc.complete_parse();
1,238✔
1436
        }
1437

1438
        if (ud.ud_file_schema.empty()) {
1,239✔
1439
            static const auto SCHEMA_LINE
1440
                = attr_line_t()
2✔
1441
                      .append(
4✔
1442
                          fmt::format(FMT_STRING("    \"$schema\": \"{}\","),
8✔
1443
                                      *SUPPORTED_FORMAT_SCHEMAS.begin()))
4✔
1444
                      .with_attr_for_all(VC_ROLE.value(role_t::VCR_QUOTED_CODE))
4✔
1445
                      .move();
4✔
1446

1447
            errors.emplace_back(
2✔
1448
                lnav::console::user_message::warning(
×
1449
                    attr_line_t("format file is missing ")
4✔
1450
                        .append_quoted("$schema"_symbol)
2✔
1451
                        .append(" property"))
2✔
1452
                    .with_snippet(lnav::console::snippet::from(
6✔
1453
                        intern_string::lookup(filename.string()), ""))
4✔
1454
                    .with_note("the schema specifies the supported format "
4✔
1455
                               "version and can be used with editors to "
1456
                               "automatically validate the file")
1457
                    .with_help(attr_line_t("add the following property to the "
4✔
1458
                                           "top-level JSON object:\n")
1459
                                   .append(SCHEMA_LINE)));
2✔
1460
        }
1461
    }
1,239✔
1462

1463
    return retval;
2,478✔
1464
}
1,239✔
1465

1466
static void
1467
load_from_path(const std::filesystem::path& path,
2,246✔
1468
               std::vector<lnav::console::user_message>& errors)
1469
{
1470
    auto format_path = path / "formats/*/*.json";
2,246✔
1471
    static_root_mem<glob_t, globfree> gl;
2,246✔
1472

1473
    log_info("loading formats from path: %s", format_path.c_str());
2,246✔
1474
    if (glob(format_path.c_str(), 0, nullptr, gl.inout()) == 0) {
2,246✔
1475
        for (int lpc = 0; lpc < (int) gl->gl_pathc; lpc++) {
1,340✔
1476
            auto filepath = std::filesystem::path(gl->gl_pathv[lpc]);
1,236✔
1477

1478
            if (startswith(filepath.filename().string(), "config.")) {
1,236✔
1479
                log_info("  not loading config as format: %s",
×
1480
                         filepath.c_str());
1481
                continue;
×
1482
            }
1483

1484
            auto format_list = load_format_file(filepath, errors);
1,236✔
1485
            if (format_list.empty()) {
1,236✔
1486
                log_warning("Empty format file: %s", filepath.c_str());
2✔
1487
            } else {
1488
                log_info("contents of format file '%s':", filepath.c_str());
1,234✔
1489
                for (auto iter = format_list.begin(); iter != format_list.end();
2,570✔
1490
                     ++iter)
1,336✔
1491
                {
1492
                    log_info("  found format: %s", iter->get());
1,336✔
1493
                }
1494
            }
1495
        }
1,236✔
1496
    }
1497
}
2,246✔
1498

1499
void
1500
load_formats(const std::vector<std::filesystem::path>& extra_paths,
829✔
1501
             std::vector<lnav::console::user_message>& errors)
1502
{
1503
    auto op_guard = lnav_opid_guard::once(__FUNCTION__);
829✔
1504

1505
    auto default_source = lnav::paths::dotlnav() / "default";
829✔
1506
    std::vector<intern_string_t> retval;
829✔
1507
    loader_userdata ud;
829✔
1508
    yajl_handle handle;
1509

1510
    write_sample_file();
829✔
1511

1512
    log_debug("Loading default formats");
829✔
1513
    for (const auto& bsf : lnav_format_json) {
60,517✔
1514
        yajlpp_parse_context ypc_builtin(intern_string::lookup(bsf.get_name()),
59,688✔
1515
                                         &root_format_handler);
59,688✔
1516
        handle = yajl_alloc(&ypc_builtin.ypc_callbacks, nullptr, &ypc_builtin);
59,688✔
1517
        ud.ud_parse_context = &ypc_builtin;
59,688✔
1518
        ud.ud_format_names = &retval;
59,688✔
1519
        ud.ud_errors = &errors;
59,688✔
1520
        ypc_builtin.with_obj(ud)
119,376✔
1521
            .with_handle(handle)
59,688✔
1522
            .with_error_reporter(format_error_reporter)
59,688✔
1523
            .ypc_userdata = &ud;
119,376✔
1524
        yajl_config(handle, yajl_allow_comments, 1);
59,688✔
1525
        auto sf = bsf.to_string_fragment_producer();
59,688✔
1526
        ypc_builtin.parse(*sf);
59,688✔
1527
        yajl_free(handle);
59,688✔
1528
    }
59,688✔
1529

1530
    for (const auto& extra_path : extra_paths) {
3,075✔
1531
        load_from_path(extra_path, errors);
2,246✔
1532
    }
1533

1534
    std::vector<std::shared_ptr<external_log_format>> alpha_ordered_formats;
829✔
1535
    for (auto iter = LOG_FORMATS.begin(); iter != LOG_FORMATS.end(); ++iter) {
61,751✔
1536
        auto& elf = iter->second;
60,922✔
1537
        elf->build(errors);
60,922✔
1538

1539
        for (auto& check_iter : LOG_FORMATS) {
4,550,910✔
1540
            if (iter->first == check_iter.first) {
4,489,988✔
1541
                continue;
60,922✔
1542
            }
1543

1544
            auto& check_elf = check_iter.second;
4,429,066✔
1545
            if (elf->match_samples(check_elf->elf_samples)) {
4,429,066✔
1546
                log_warning(
17,403✔
1547
                    "Format collision, format '%s' matches sample from '%s'",
1548
                    elf->get_name().get(),
1549
                    check_elf->get_name().get());
1550
                elf->elf_collision.push_back(check_elf->get_name());
17,403✔
1551
            }
1552
        }
1553

1554
        alpha_ordered_formats.push_back(elf);
60,922✔
1555
    }
1556

1557
    auto& graph_ordered_formats = external_log_format::GRAPH_ORDERED_FORMATS;
829✔
1558

1559
    while (!alpha_ordered_formats.empty()) {
10,567✔
1560
        std::vector<intern_string_t> popped_formats;
9,738✔
1561

1562
        for (auto iter = alpha_ordered_formats.begin();
9,738✔
1563
             iter != alpha_ordered_formats.end();)
111,579✔
1564
        {
1565
            auto elf = *iter;
101,841✔
1566
            if (elf->elf_collision.empty()) {
101,841✔
1567
                iter = alpha_ordered_formats.erase(iter);
60,922✔
1568
                popped_formats.push_back(elf->get_name());
60,922✔
1569
                graph_ordered_formats.push_back(elf);
60,922✔
1570
            } else {
1571
                ++iter;
40,919✔
1572
            }
1573
        }
101,841✔
1574

1575
        if (popped_formats.empty() && !alpha_ordered_formats.empty()) {
9,738✔
1576
            bool broke_cycle = false;
2,796✔
1577

1578
            log_warning("Detected a cycle...");
2,796✔
1579
            for (const auto& elf : alpha_ordered_formats) {
16,357✔
1580
                if (elf->elf_builtin_format) {
13,870✔
1581
                    log_warning("  Skipping builtin format -- %s",
13,561✔
1582
                                elf->get_name().get());
1583
                } else {
1584
                    log_warning("  Breaking cycle by picking -- %s",
309✔
1585
                                elf->get_name().get());
1586
                    elf->elf_collision.clear();
309✔
1587
                    broke_cycle = true;
309✔
1588
                    break;
309✔
1589
                }
1590
            }
1591
            if (!broke_cycle) {
2,796✔
1592
                alpha_ordered_formats.front()->elf_collision.clear();
2,487✔
1593
            }
1594
        }
1595

1596
        for (const auto& elf : alpha_ordered_formats) {
50,657✔
1597
            for (auto& popped_format : popped_formats) {
467,460✔
1598
                elf->elf_collision.remove(popped_format);
426,541✔
1599
            }
1600
        }
1601
    }
9,738✔
1602

1603
    log_info("Format order:") for (auto& graph_ordered_format :
1,658✔
1604
                                   graph_ordered_formats)
62,580✔
1605
    {
1606
        log_info("  %s", graph_ordered_format->get_name().get());
60,922✔
1607
    }
1608

1609
    auto& roots = log_format::get_root_formats();
829✔
1610
    auto iter = std::find_if(roots.begin(), roots.end(), [](const auto& elem) {
829✔
1611
        return elem->get_name() == "generic_log";
4,145✔
1612
    });
1613
    roots.insert(
829✔
1614
        iter, graph_ordered_formats.begin(), graph_ordered_formats.end());
1615
}
829✔
1616

1617
static void
1618
exec_sql_in_path(sqlite3* db,
2,199✔
1619
                 const std::map<std::string, scoped_value_t>& global_vars,
1620
                 const std::filesystem::path& path,
1621
                 std::vector<lnav::console::user_message>& errors)
1622
{
1623
    auto format_path = path / "formats/*/*.sql";
2,199✔
1624
    static_root_mem<glob_t, globfree> gl;
2,199✔
1625

1626
    log_info("executing SQL files in path: %s", format_path.c_str());
2,199✔
1627
    if (glob(format_path.c_str(), 0, nullptr, gl.inout()) == 0) {
2,199✔
1628
        for (int lpc = 0; lpc < (int) gl->gl_pathc; lpc++) {
1,541✔
1629
            auto filename = std::filesystem::path(gl->gl_pathv[lpc]);
771✔
1630
            auto read_res = lnav::filesystem::read_file(filename);
771✔
1631

1632
            if (read_res.isOk()) {
771✔
1633
                log_info("Executing SQL file: %s", filename.c_str());
771✔
1634
                auto content = read_res.unwrap();
771✔
1635

1636
                sql_execute_script(
771✔
1637
                    db, global_vars, filename.c_str(), content.c_str(), errors);
1638
            } else {
771✔
1639
                errors.emplace_back(
×
1640
                    lnav::console::user_message::error(
×
1641
                        attr_line_t("unable to read format file: ")
×
1642
                            .append(lnav::roles::file(filename.string())))
×
1643
                        .with_reason(read_res.unwrapErr()));
×
1644
            }
1645
        }
771✔
1646
    }
1647
}
2,199✔
1648

1649
void
1650
load_format_extra(sqlite3* db,
714✔
1651
                  const std::map<std::string, scoped_value_t>& global_vars,
1652
                  const std::vector<std::filesystem::path>& extra_paths,
1653
                  std::vector<lnav::console::user_message>& errors)
1654
{
1655
    for (const auto& extra_path : extra_paths) {
2,913✔
1656
        exec_sql_in_path(db, global_vars, extra_path, errors);
2,199✔
1657
    }
1658
}
714✔
1659

1660
static void
1661
extract_metadata(string_fragment contents, script_metadata& meta_out)
18,276✔
1662
{
1663
    static const auto SYNO_RE = lnav::pcre2pp::code::from_const(
1664
        "^#\\s+@synopsis:(.*)$", PCRE2_MULTILINE);
18,276✔
1665
    static const auto DESC_RE = lnav::pcre2pp::code::from_const(
1666
        "^#\\s+@description:(.*)$", PCRE2_MULTILINE);
18,276✔
1667
    static const auto OUTPUT_FORMAT_RE = lnav::pcre2pp::code::from_const(
1668
        "^#\\s+@output-format:\\s+(.*)$", PCRE2_MULTILINE);
18,276✔
1669

1670
    auto syno_md = SYNO_RE.create_match_data();
18,276✔
1671
    auto syno_match_res
1672
        = SYNO_RE.capture_from(contents).into(syno_md).matches().ignore_error();
18,276✔
1673
    if (syno_match_res) {
18,276✔
1674
        meta_out.sm_synopsis = syno_md[1]->trim().to_string();
18,237✔
1675
    }
1676
    auto desc_md = DESC_RE.create_match_data();
18,276✔
1677
    auto desc_match_res
1678
        = DESC_RE.capture_from(contents).into(desc_md).matches().ignore_error();
18,276✔
1679
    if (desc_match_res) {
18,276✔
1680
        meta_out.sm_description = desc_md[1]->trim().to_string();
18,237✔
1681
    }
1682

1683
    auto out_format_md = OUTPUT_FORMAT_RE.create_match_data();
18,276✔
1684
    auto out_format_res = OUTPUT_FORMAT_RE.capture_from(contents)
18,276✔
1685
                              .into(out_format_md)
18,276✔
1686
                              .matches()
36,552✔
1687
                              .ignore_error();
18,276✔
1688
    if (out_format_res) {
18,276✔
1689
        auto out_format_frag = out_format_md[1]->trim();
870✔
1690
        auto from_res = from<text_format_t>(out_format_frag);
870✔
1691
        if (from_res.isErr()) {
870✔
1692
            log_error("%s (%s): invalid @output-format '%.*s'",
×
1693
                      meta_out.sm_name.c_str(),
1694
                      meta_out.sm_path.c_str(),
1695
                      out_format_frag.length(),
1696
                      out_format_frag.data());
1697
        } else {
1698
            meta_out.sm_output_format = from_res.unwrap();
870✔
1699
            log_info("%s (%s): setting output format to %d",
870✔
1700
                     meta_out.sm_name.c_str(),
1701
                     meta_out.sm_path.c_str(),
1702
                     meta_out.sm_output_format);
1703
        }
1704
    }
870✔
1705

1706
    if (!meta_out.sm_synopsis.empty()) {
18,276✔
1707
        size_t space = meta_out.sm_synopsis.find(' ');
18,237✔
1708

1709
        if (space == std::string::npos) {
18,237✔
1710
            space = meta_out.sm_synopsis.size();
13,928✔
1711
        }
1712
        meta_out.sm_name = meta_out.sm_synopsis.substr(0, space);
18,237✔
1713
    }
1714
}
18,276✔
1715

1716
void
1717
extract_metadata_from_file(struct script_metadata& meta_inout)
867✔
1718
{
1719
    auto stat_res = lnav::filesystem::stat_file(meta_inout.sm_path);
867✔
1720
    if (stat_res.isErr()) {
867✔
1721
        log_warning("unable to open script: %s -- %s",
×
1722
                    meta_inout.sm_path.c_str(),
1723
                    stat_res.unwrapErr().c_str());
1724
        return;
×
1725
    }
1726

1727
    auto st = stat_res.unwrap();
867✔
1728
    if (!S_ISREG(st.st_mode)) {
867✔
1729
        log_warning("script is not a regular file -- %s",
×
1730
                    meta_inout.sm_path.c_str());
1731
        return;
×
1732
    }
1733

1734
    auto open_res = lnav::filesystem::open_file(meta_inout.sm_path, O_RDONLY);
867✔
1735
    if (open_res.isErr()) {
867✔
1736
        log_warning("unable to open script file: %s -- %s",
×
1737
                    meta_inout.sm_path.c_str(),
1738
                    open_res.unwrapErr().c_str());
1739
        return;
×
1740
    }
1741

1742
    auto fd = open_res.unwrap();
867✔
1743
    char buffer[8 * 1024];
1744
    auto rc = read(fd, buffer, sizeof(buffer));
867✔
1745
    if (rc > 0) {
867✔
1746
        extract_metadata(string_fragment::from_bytes(buffer, rc), meta_inout);
867✔
1747
    }
1748
}
867✔
1749

1750
static void
1751
find_format_in_path(const std::filesystem::path& path,
131✔
1752
                    available_scripts& scripts)
1753
{
1754
    for (const auto& format_path :
393✔
1755
         {path / "formats/*/*.lnav", path / "configs/*/*.lnav"})
786✔
1756
    {
1757
        static_root_mem<glob_t, globfree> gl;
262✔
1758

1759
        log_debug("Searching for script in path: %s", format_path.c_str());
262✔
1760
        if (glob(format_path.c_str(), 0, nullptr, gl.inout()) == 0) {
262✔
1761
            for (size_t lpc = 0; lpc < gl->gl_pathc; lpc++) {
917✔
1762
                const char* filename = basename(gl->gl_pathv[lpc]);
860✔
1763
                auto script_name = std::string(filename, strlen(filename) - 5);
860✔
1764
                struct script_metadata meta;
860✔
1765

1766
                meta.sm_path = gl->gl_pathv[lpc];
860✔
1767
                meta.sm_name = script_name;
860✔
1768
                extract_metadata_from_file(meta);
860✔
1769
                scripts.as_scripts[script_name].push_back(meta);
860✔
1770

1771
                log_info("  found script: %s", meta.sm_path.c_str());
860✔
1772
            }
860✔
1773
        }
1774
    }
655✔
1775
}
131✔
1776

1777
available_scripts
1778
find_format_scripts(const std::vector<std::filesystem::path>& extra_paths)
41✔
1779
{
1780
    available_scripts retval;
41✔
1781
    for (const auto& extra_path : extra_paths) {
172✔
1782
        find_format_in_path(extra_path, retval);
131✔
1783
    }
1784
    return retval;
41✔
1785
}
×
1786

1787
void
1788
load_format_vtabs(log_vtab_manager* vtab_manager,
714✔
1789
                  std::vector<lnav::console::user_message>& errors)
1790
{
1791
    auto& root_formats = LOG_FORMATS;
714✔
1792

1793
    for (auto& root_format : root_formats) {
52,792✔
1794
        root_format.second->register_vtabs(vtab_manager, errors);
52,078✔
1795
    }
1796
}
714✔
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