Skip to content

Commit 94d8438

Browse files
committed
[formats] add windows event log tabular format
Related to #557
1 parent 518d483 commit 94d8438

15 files changed

Lines changed: 186 additions & 35 deletions

src/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ set(TIME_FORMATS
119119
"%m/%e/%Y %l:%M:%S %p%z"
120120
"%m/%e/%Y %l:%M:%S %p %z"
121121
"%m/%e/%Y %I:%M:%S %p"
122+
"%m/%e/%Y %l:%M:%S %p"
122123
"%m/%e/%Y %l:%M:%S%p"
123124
"%m/%d/%y %H:%M:%S"
124125
"%m/%d/%Y %H:%M:%S"
@@ -240,6 +241,7 @@ set(FORMAT_FILES
240241
formats/vmw_vc_svc_log.json
241242
formats/vmw_py_log.json
242243
formats/vpostgres_log.json
244+
formats/windows_event_log.json
243245
formats/xmlrpc_log.json
244246
formats/zookeeper_log.json
245247
)

src/base/file_range.hh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ public:
6767
this->fr_size = 0;
6868
}
6969

70-
ssize_t next_offset() const { return this->fr_offset + this->fr_size; }
70+
file_ssize_t next_offset() const { return this->fr_offset + this->fr_size; }
7171

7272
bool empty() const { return this->fr_size == 0; }
7373
};

src/formats/formats.am

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ FORMAT_FILES = \
6868
$(srcdir)/%reldir%/vmw_py_log.json \
6969
$(srcdir)/%reldir%/vpostgres_log.json \
7070
$(srcdir)/%reldir%/web_robot_log.json \
71+
$(srcdir)/%reldir%/windows_event_log.json \
7172
$(srcdir)/%reldir%/xmlrpc_log.json \
7273
$(srcdir)/%reldir%/zap_console_log.json \
7374
$(srcdir)/%reldir%/zellij_log.json \

src/formats/windows_event_log.json

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
{
2+
"$schema": "https://lnav.org/schemas/format-v1.schema.json",
3+
"windows_event_log": {
4+
"title": "Windows Event Log (PowerShell CSV)",
5+
"description": "Windows event log exported via `Get-WinEvent | Export-Csv -NoTypeInformation`. Multi-line `Message` cells need to be flattened to a single line before export, e.g. `Get-WinEvent ... | Select-Object *, @{n='Message';e={$_.Message -replace '[\\r\\n]+',' '}} -ExcludeProperty Message | Export-Csv ...`.",
6+
"url": "https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.diagnostics/get-winevent",
7+
"file-type": "tabular",
8+
"timestamp-field": "TimeCreated",
9+
"level-field": "LevelDisplayName",
10+
"body-field": "Message",
11+
"opid-field": "ActivityId",
12+
"value": {
13+
"Message": {
14+
"kind": "string"
15+
},
16+
"Id": {
17+
"kind": "integer",
18+
"identifier": true
19+
},
20+
"Version": {
21+
"kind": "integer"
22+
},
23+
"Qualifiers": {
24+
"kind": "integer"
25+
},
26+
"Level": {
27+
"kind": "integer"
28+
},
29+
"Task": {
30+
"kind": "integer"
31+
},
32+
"Opcode": {
33+
"kind": "integer"
34+
},
35+
"Keywords": {
36+
"kind": "string"
37+
},
38+
"RecordId": {
39+
"kind": "integer",
40+
"foreign-key": true
41+
},
42+
"ProviderName": {
43+
"kind": "string",
44+
"identifier": true
45+
},
46+
"ProviderId": {
47+
"kind": "string"
48+
},
49+
"LogName": {
50+
"kind": "string",
51+
"identifier": true
52+
},
53+
"ProcessId": {
54+
"kind": "integer",
55+
"identifier": true
56+
},
57+
"ThreadId": {
58+
"kind": "integer",
59+
"identifier": true
60+
},
61+
"MachineName": {
62+
"kind": "string",
63+
"identifier": true
64+
},
65+
"UserId": {
66+
"kind": "string",
67+
"identifier": true
68+
},
69+
"TimeCreated": {
70+
"kind": "string"
71+
},
72+
"ActivityId": {
73+
"kind": "string",
74+
"identifier": true
75+
},
76+
"RelatedActivityId": {
77+
"kind": "string",
78+
"identifier": true
79+
},
80+
"ContainerLog": {
81+
"kind": "string"
82+
},
83+
"MatchedQueryIds": {
84+
"kind": "string"
85+
},
86+
"Bookmark": {
87+
"kind": "string"
88+
},
89+
"LevelDisplayName": {
90+
"kind": "string"
91+
},
92+
"OpcodeDisplayName": {
93+
"kind": "string"
94+
},
95+
"TaskDisplayName": {
96+
"kind": "string"
97+
},
98+
"KeywordsDisplayNames": {
99+
"kind": "string"
100+
}
101+
}
102+
}
103+
}

src/log_format.cc

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2231,7 +2231,7 @@ external_log_format::scan_tabular(logfile& lf,
22312231
auto sf = sbr.to_string_fragment();
22322232
if (this->lf_specialized) {
22332233
auto& ll = dst.back();
2234-
if (dst.size() <= this->tlf_header_lines) {
2234+
if (li.li_file_range.fr_offset < this->tlf_header_end) {
22352235
// ignore header
22362236
ll.set_ignore(true);
22372237
return scan_match{1000};
@@ -2318,10 +2318,9 @@ external_log_format::scan_tabular(logfile& lf,
23182318
}
23192319
if (value_index >= this->elf_value_def_read_order.size()) {
23202320
this->finalize_line(dst.back(), lfi, sbc);
2321+
} else if (sf.startswith("#")) {
2322+
ll.set_ignore(true);
23212323
} else {
2322-
if (sf.startswith("#")) {
2323-
ll.set_ignore(true);
2324-
}
23252324
ll.set_level(LEVEL_INVALID);
23262325
}
23272326

@@ -2338,7 +2337,7 @@ external_log_format::scan_tabular(logfile& lf,
23382337

23392338
auto header_state = tabular_header_state::reading_metadata;
23402339
std::optional<char> sep;
2341-
for (auto ll_iter = dst.begin(); ll_iter != dst.end(); ++ll_iter) {
2340+
for (auto ll_iter = lf.begin(); ll_iter != lf.end(); ++ll_iter) {
23422341
auto read_res = lf.read_line(ll_iter);
23432342
if (read_res.isErr()) {
23442343
return scan_no_match{"cannot read header"};
@@ -2356,17 +2355,11 @@ external_log_format::scan_tabular(logfile& lf,
23562355
return scan_error{"sep= hint missing separator character"};
23572356
}
23582357
sep = sep_sf.data()[0];
2359-
ll_iter->set_time(std::chrono::microseconds::zero());
2360-
ll_iter->set_level(LEVEL_UNKNOWN);
2361-
ll_iter->set_ignore(true);
23622358
log_info(" %ld:found 'sep=' header: %x",
23632359
std::distance(dst.begin(), ll_iter),
23642360
sep.value());
23652361
} else if (hdr_sf.startswith("#")) {
2366-
ll_iter->set_time(std::chrono::microseconds::zero());
2367-
ll_iter->set_level(LEVEL_UNKNOWN);
2368-
ll_iter->set_ignore(true);
2369-
log_info(" %ld:ignoring comment -- %.*s",
2362+
log_info(" %ld:comment header -- %.*s",
23702363
std::distance(dst.begin(), ll_iter),
23712364
hdr_sf.length(),
23722365
hdr_sf.data());
@@ -2382,10 +2375,6 @@ external_log_format::scan_tabular(logfile& lf,
23822375
ss.ss_separator = sep.value();
23832376
uint32_t hits = 0, misses = 0;
23842377

2385-
log_info(" %ld:found header -- %.*s",
2386-
std::distance(dst.begin(), ll_iter),
2387-
hdr_sf.length(),
2388-
hdr_sf.data());
23892378
this->elf_value_def_read_order.clear();
23902379
for (auto hdr_name : ss) {
23912380
auto value_iter
@@ -2403,13 +2392,25 @@ external_log_format::scan_tabular(logfile& lf,
24032392
if (hits <= 2) {
24042393
return scan_no_match{"not enough columns matched"};
24052394
}
2395+
2396+
for (auto prev_iter = dst.begin(); prev_iter != ll_iter;
2397+
++prev_iter)
2398+
{
2399+
prev_iter->set_time(std::chrono::microseconds::zero());
2400+
prev_iter->set_level(LEVEL_UNKNOWN);
2401+
prev_iter->set_ignore(true);
2402+
}
2403+
2404+
log_info(" %ld:found column header -- %.*s",
2405+
std::distance(dst.begin(), ll_iter),
2406+
hdr_sf.length(),
2407+
hdr_sf.data());
2408+
24062409
header_state = tabular_header_state::have_column_header;
2407-
auto& ll = dst.back();
2408-
ll.set_ignore(true);
2409-
ll.set_level(LEVEL_INVALID);
2410+
ll_iter->set_ignore(true);
2411+
ll_iter->set_level(LEVEL_INVALID);
24102412
this->tlf_separator = sep.value();
2411-
this->tlf_header_lines
2412-
= std::distance(dst.begin(), ll_iter) + 1;
2413+
this->tlf_header_end = li.li_file_range.next_offset();
24132414
return scan_match{1000, misses, hits};
24142415
}
24152416
}
@@ -4893,7 +4894,6 @@ external_log_format::build(std::vector<lnav::console::user_message>& errors)
48934894
yajl_config(
48944895
this->jlf_yajl_handle.get(), yajl_dont_validate_strings, 1);
48954896
} else if (this->elf_type == elf_type_t::ELF_TYPE_TABULAR) {
4896-
this->lf_multiline = false;
48974897
this->lf_structured = true;
48984898
}
48994899
} else {

src/log_format_ext.hh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -523,7 +523,7 @@ public:
523523
std::shared_ptr<yajl_handle_t> jlf_yajl_handle;
524524
shared_buffer jlf_share_manager;
525525

526-
size_t tlf_header_lines{0};
526+
file_ssize_t tlf_header_end;
527527
char tlf_separator{','};
528528

529529
private:

src/logfile.cc

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -861,15 +861,25 @@ logfile::process_prefix(shared_buffer_ref& sbr,
861861
if (this->lf_format == nullptr && li.li_timestamp.tv_sec != 0) {
862862
last_time = to_us(li.li_timestamp);
863863
last_level = li.li_level;
864-
} else if (!this->lf_index.empty()) {
865-
const auto& ll = this->lf_index.back();
864+
} else {
865+
for (auto riter = this->lf_index.rbegin();
866+
riter != this->lf_index.rend();
867+
++riter)
868+
{
869+
if (riter->is_ignored()
870+
|| riter->get_msg_level() == LEVEL_INVALID)
871+
{
872+
continue;
873+
}
866874

867-
/*
868-
* Assume this line is part of the previous one(s) and copy the
869-
* metadata over.
870-
*/
871-
last_time = ll.get_time();
872-
last_level = ll.get_msg_level();
875+
/*
876+
* Assume this line is part of the previous one(s) and copy the
877+
* metadata over.
878+
*/
879+
last_time = riter->get_time();
880+
last_level = riter->get_msg_level();
881+
break;
882+
}
873883
}
874884
this->lf_index.emplace_back(
875885
li.li_file_range.fr_offset, last_time, last_level);
@@ -1313,10 +1323,16 @@ logfile::process_prefix(shared_buffer_ref& sbr,
13131323
}
13141324
} else if (found.is<log_format::scan_no_match>()) {
13151325
if (this->lf_format.get() != nullptr) {
1326+
auto& ll = this->lf_index.back();
13161327
if (this->lf_format->lf_multiline) {
1317-
this->lf_index.back().set_continued(true);
1328+
if (this->lf_index.size() > 1) {
1329+
auto& prev_ll = this->lf_index[this->lf_index.size() - 2];
1330+
if (prev_ll.is_continued() || !prev_ll.is_ignored()) {
1331+
ll.set_continued(true);
1332+
}
1333+
}
13181334
} else {
1319-
this->lf_index.back().set_level(LEVEL_INVALID);
1335+
ll.set_level(LEVEL_INVALID);
13201336
}
13211337
}
13221338
}

src/time_formats.am

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ TIME_FORMATS = \
8888
"%m/%e/%Y %l:%M:%S %p%z" \
8989
"%m/%e/%Y %l:%M:%S %p %z" \
9090
"%m/%e/%Y %I:%M:%S %p" \
91+
"%m/%e/%Y %l:%M:%S %p" \
9192
"%m/%e/%Y %l:%M:%S%p" \
9293
"%m/%d/%y %H:%M:%S" \
9394
"%m/%d/%Y %H:%M:%S" \

src/view_helpers.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1501,7 +1501,7 @@ hist_index_delegate::index_line(logfile_sub_source& lss,
15011501
logfile* lf,
15021502
logfile::iterator ll)
15031503
{
1504-
if (ll->is_continued()
1504+
if (!ll->is_message()
15051505
|| ll->get_time<>() == std::chrono::microseconds::zero())
15061506
{
15071507
return;

test/Makefile.am

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,7 @@ dist_noinst_DATA = \
463463
logfile_w3c.5 \
464464
logfile_w3c.6 \
465465
logfile_w3c_big.0 \
466+
logfile_win_events_csv.0 \
466467
logfile_with_a_really_long_name_to_test_a_bug_with_long_names.0 \
467468
logfile_with_zones.0 \
468469
logfile_xml_msg.0 \

0 commit comments

Comments
 (0)