Rollback from r3608 to r3606
[public/netxms.git] / src / libnxlp / parser.cpp
CommitLineData
5039dede
AK
1/*
2** NetXMS - Network Management System
3** Log Parsing Library
4** Copyright (C) 2008 Victor Kirhenshtein
5**
6** This program is free software; you can redistribute it and/or modify
7** it under the terms of the GNU General Public License as published by
8** the Free Software Foundation; either version 2 of the License, or
9** (at your option) any later version.
10**
11** This program is distributed in the hope that it will be useful,
12** but WITHOUT ANY WARRANTY; without even the implied warranty of
13** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14** GNU General Public License for more details.
15**
16** You should have received a copy of the GNU General Public License
17** along with this program; if not, write to the Free Software
18** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19**
20** File: parser.cpp
21**
22**/
23
24#include "libnxlp.h"
25
26#if HAVE_LIBEXPAT
27#include <expat.h>
28#endif
29
30
31//
32// Context state texts
33//
34
35static const TCHAR *m_states[] = { _T("MANUAL"), _T("AUTO"), _T("INACTIVE") };
36
37
38//
39// XML parser state for creating LogParser object from XML
40//
41
42#define XML_STATE_INIT -1
43#define XML_STATE_END -2
44#define XML_STATE_ERROR -255
45#define XML_STATE_PARSER 0
46#define XML_STATE_RULES 1
47#define XML_STATE_RULE 2
48#define XML_STATE_MATCH 3
49#define XML_STATE_EVENT 4
50#define XML_STATE_FILE 5
51#define XML_STATE_ID 6
52#define XML_STATE_LEVEL 7
53#define XML_STATE_SOURCE 8
54#define XML_STATE_CONTEXT 9
55#define XML_STATE_MACROS 10
56#define XML_STATE_MACRO 11
57#define XML_STATE_DESCRIPTION 12
58
59
60typedef struct
61{
62 LogParser *parser;
63 int state;
64 String regexp;
65 String event;
66 String file;
67 String id;
68 String level;
69 String source;
70 String context;
71 String description;
72 int contextAction;
73 String ruleContext;
74 int numEventParams;
75 String errorText;
76 String macroName;
77 String macro;
78 BOOL invertedRule;
79 BOOL breakFlag;
80} XML_PARSER_STATE;
81
82
83//
84// Parser default constructor
85//
86
87LogParser::LogParser()
88{
89 m_numRules = 0;
90 m_rules = NULL;
91 m_cb = NULL;
92 m_userArg = NULL;
93 m_fileName = NULL;
94 m_eventNameList = NULL;
95 m_eventResolver = NULL;
96 m_thread = INVALID_THREAD_HANDLE;
97 m_recordsProcessed = 0;
98 m_recordsMatched = 0;
99 m_processAllRules = FALSE;
100 m_traceLevel = 0;
101 m_traceCallback = NULL;
102}
103
104
105//
106// Destructor
107//
108
109LogParser::~LogParser()
110{
111 int i;
112
113 for(i = 0; i < m_numRules; i++)
114 delete m_rules[i];
115 safe_free(m_rules);
116 safe_free(m_fileName);
117}
118
119
120//
121// Trace
122//
123
124void LogParser::Trace(int level, const TCHAR *format, ...)
125{
126 va_list args;
127
128 if ((m_traceCallback == NULL) || (level > m_traceLevel))
129 return;
130
131 va_start(args, format);
132 m_traceCallback(format, args);
133 va_end(args);
134}
135
136
137//
138// Add rule
139//
140
141BOOL LogParser::AddRule(LogParserRule *rule)
142{
143 BOOL isOK;
144
145 isOK = rule->IsValid();
146 if (isOK)
147 {
148 m_rules = (LogParserRule **)realloc(m_rules, sizeof(LogParserRule *) * (m_numRules + 1));
149 m_rules[m_numRules++] = rule;
150 }
151 else
152 {
153 delete rule;
154 }
155 return isOK;
156}
157
158BOOL LogParser::AddRule(const char *regexp, DWORD event, int numParams)
159{
160 return AddRule(new LogParserRule(this, regexp, event, numParams));
161}
162
163
164//
165// Check context
166//
167
168const TCHAR *LogParser::CheckContext(LogParserRule *rule)
169{
170 const TCHAR *state;
171
172 if (rule->GetContext() == NULL)
173 return m_states[CONTEXT_SET_MANUAL];
174
175 state = m_contexts.Get(rule->GetContext());
176 if (state == NULL)
177 return NULL; // Context inactive, don't use this rule
178
179 return !_tcscmp(state, m_states[CONTEXT_CLEAR]) ? NULL : state;
180}
181
182
183//
184// Match log line
185//
186
187BOOL LogParser::MatchLine(const char *line, DWORD objectId)
188{
189 int i;
190 const TCHAR *state;
191 BOOL matched = FALSE;
192
193 Trace(2, _T("Match line: \"%s\""), line);
194 m_recordsProcessed++;
195 for(i = 0; i < m_numRules; i++)
196 {
197 Trace(4, _T("checking rule %d \"%s\""), i + 1, m_rules[i]->GetDescription());
198 if (((state = CheckContext(m_rules[i])) != NULL) && m_rules[i]->Match(line, m_cb, objectId, m_userArg))
199 {
200 Trace(1, _T("rule %d \"%s\" matched"), i + 1, m_rules[i]->GetDescription());
201 if (!matched)
202 m_recordsMatched++;
203
204 // Update context
205 if (m_rules[i]->GetContextToChange() != NULL)
206 {
207 m_contexts.Set(m_rules[i]->GetContextToChange(), m_states[m_rules[i]->GetContextAction()]);
208 Trace(2, _T("rule %d \"%s\": context %s set to %s"), i + 1, m_rules[i]->GetDescription(), m_rules[i]->GetContextToChange(), m_states[m_rules[i]->GetContextAction()]);
209 }
210
211 // Set context of this rule to inactive if rule context mode is "automatic reset"
212 if (!_tcscmp(state, m_states[CONTEXT_SET_AUTOMATIC]))
213 {
214 m_contexts.Set(m_rules[i]->GetContext(), m_states[CONTEXT_CLEAR]);
215 Trace(2, _T("rule %d \"%s\": context %s cleared because it was set to automatic reset mode"),
216 i + 1, m_rules[i]->GetDescription(), m_rules[i]->GetContext());
217 }
218 matched = TRUE;
219 if (!m_processAllRules || m_rules[i]->GetBreakFlag())
220 break;
221 }
222 }
223 if (i < m_numRules)
224 Trace(2, _T("processing stopped at rule %d \"%s\"; result = %s"), i + 1,
225 m_rules[i]->GetDescription(), matched ? _T("true") : _T("false"));
226 else
227 Trace(2, _T("Processing stopped at end of rules list; result = %s"), matched ? _T("true") : _T("false"));
228 return matched;
229}
230
231
232//
233// Set associated file name
234//
235
236void LogParser::SetFileName(const TCHAR *name)
237{
238 safe_free(m_fileName);
239 m_fileName = (name != NULL) ? _tcsdup(name) : NULL;
240}
241
242
243//
244// Add macro
245//
246
247void LogParser::AddMacro(const TCHAR *name, const TCHAR *value)
248{
249 m_macros.Set(name, value);
250}
251
252
253//
254// Get macro
255//
256
257const TCHAR *LogParser::GetMacro(const TCHAR *name)
258{
259 const TCHAR *value;
260
261 value = m_macros.Get(name);
262 return CHECK_NULL_EX(value);
263}
264
265
266//
267// Create parser configuration from XML
268//
269
270#if HAVE_LIBEXPAT
271
272static void StartElement(void *userData, const char *name, const char **attrs)
273{
274 XML_PARSER_STATE *ps = (XML_PARSER_STATE *)userData;
275
276 if (!strcmp(name, "parser"))
277 {
278 ps->state = XML_STATE_PARSER;
279 ps->parser->SetProcessAllFlag(XMLGetAttrBoolean(attrs, "processAll", FALSE));
280 ps->parser->SetTraceLevel(XMLGetAttrInt(attrs, "trace", 0));
281 }
282 else if (!strcmp(name, "file"))
283 {
284 ps->state = XML_STATE_FILE;
285 }
286 else if (!strcmp(name, "macros"))
287 {
288 ps->state = XML_STATE_MACROS;
289 }
290 else if (!strcmp(name, "macro"))
291 {
292 const char *name;
293
294 ps->state = XML_STATE_MACRO;
295 name = XMLGetAttr(attrs, "name");
296 ps->macroName = CHECK_NULL_A(name);
297 ps->macro = NULL;
298 }
299 else if (!strcmp(name, "rules"))
300 {
301 ps->state = XML_STATE_RULES;
302 }
303 else if (!strcmp(name, "rule"))
304 {
305 ps->regexp = NULL;
306 ps->event = NULL;
307 ps->context = NULL;
308 ps->contextAction = CONTEXT_SET_AUTOMATIC;
309 ps->description = NULL;
310 ps->ruleContext = XMLGetAttr(attrs, "context");
311 ps->breakFlag = XMLGetAttrBoolean(attrs, "break", FALSE);
312 ps->state = XML_STATE_RULE;
313 }
314 else if (!strcmp(name, "match"))
315 {
316 ps->state = XML_STATE_MATCH;
317 ps->invertedRule = XMLGetAttrBoolean(attrs, "invert", FALSE);
318 }
319 else if (!strcmp(name, "id"))
320 {
321 ps->state = XML_STATE_ID;
322 }
323 else if (!strcmp(name, "level"))
324 {
325 ps->state = XML_STATE_LEVEL;
326 }
327 else if (!strcmp(name, "source"))
328 {
329 ps->state = XML_STATE_SOURCE;
330 }
331 else if (!strcmp(name, "event"))
332 {
333 ps->numEventParams = XMLGetAttrDWORD(attrs, "params", 0);
334 ps->state = XML_STATE_EVENT;
335 }
336 else if (!strcmp(name, "context"))
337 {
338 const char *action;
339
340 ps->state = XML_STATE_CONTEXT;
341
342 action = XMLGetAttr(attrs, "action");
343 if (action == NULL)
344 action = "set";
345
346 if (!strcmp(action, "set"))
347 {
348 const char *mode;
349
350 mode = XMLGetAttr(attrs, "reset");
351 if (mode == NULL)
352 mode = "auto";
353
354 if (!strcmp(mode, "auto"))
355 {
356 ps->contextAction = CONTEXT_SET_AUTOMATIC;
357 }
358 else if (!strcmp(mode, "manual"))
359 {
360 ps->contextAction = CONTEXT_SET_MANUAL;
361 }
362 else
363 {
364 ps->errorText = _T("Invalid context reset mode");
365 ps->state = XML_STATE_ERROR;
366 }
367 }
368 else if (!strcmp(action, "clear"))
369 {
370 ps->contextAction = CONTEXT_CLEAR;
371 }
372 else
373 {
374 ps->errorText = _T("Invalid context action");
375 ps->state = XML_STATE_ERROR;
376 }
377 }
378 else if (!strcmp(name, "description"))
379 {
380 ps->state = XML_STATE_DESCRIPTION;
381 }
382 else
383 {
384 ps->state = XML_STATE_ERROR;
385 }
386}
387
388static void EndElement(void *userData, const char *name)
389{
390 XML_PARSER_STATE *ps = (XML_PARSER_STATE *)userData;
391
392 if (!strcmp(name, "parser"))
393 {
394 ps->state = XML_STATE_END;
395 }
396 else if (!strcmp(name, "file"))
397 {
398 ps->parser->SetFileName(ps->file);
399 ps->state = XML_STATE_PARSER;
400 }
401 else if (!strcmp(name, "macros"))
402 {
403 ps->state = XML_STATE_PARSER;
404 }
405 else if (!strcmp(name, "macro"))
406 {
407 ps->parser->AddMacro(ps->macroName, ps->macro);
408 ps->state = XML_STATE_MACROS;
409 }
410 else if (!strcmp(name, "rules"))
411 {
412 ps->state = XML_STATE_PARSER;
413 }
414 else if (!strcmp(name, "rule"))
415 {
416 DWORD event;
417 char *eptr;
418 LogParserRule *rule;
419
420 ps->event.Strip();
421 event = strtoul(ps->event, &eptr, 0);
422 if (*eptr != 0)
423 event = ps->parser->ResolveEventName(ps->event);
424 rule = new LogParserRule(ps->parser, (const char *)ps->regexp, event, ps->numEventParams);
425 if (!ps->ruleContext.IsEmpty())
426 rule->SetContext(ps->ruleContext);
427 if (!ps->context.IsEmpty())
428 {
429 rule->SetContextToChange(ps->context);
430 rule->SetContextAction(ps->contextAction);
431 }
432 if (!ps->description.IsEmpty())
433 rule->SetDescription(ps->description);
434 rule->SetInverted(ps->invertedRule);
435 rule->SetBreakFlag(ps->breakFlag);
436 ps->parser->AddRule(rule);
437 ps->state = XML_STATE_RULES;
438 }
439 else if (!strcmp(name, "match"))
440 {
441 ps->state = XML_STATE_RULE;
442 }
443 else if (!strcmp(name, "id"))
444 {
445 ps->state = XML_STATE_RULE;
446 }
447 else if (!strcmp(name, "level"))
448 {
449 ps->state = XML_STATE_RULE;
450 }
451 else if (!strcmp(name, "source"))
452 {
453 ps->state = XML_STATE_RULE;
454 }
455 else if (!strcmp(name, "event"))
456 {
457 ps->state = XML_STATE_RULE;
458 }
459 else if (!strcmp(name, "context"))
460 {
461 ps->state = XML_STATE_RULE;
462 }
463 else if (!strcmp(name, "description"))
464 {
465 ps->state = XML_STATE_RULE;
466 }
467}
468
469static void CharData(void *userData, const XML_Char *s, int len)
470{
471 XML_PARSER_STATE *ps = (XML_PARSER_STATE *)userData;
472
473 switch(ps->state)
474 {
475 case XML_STATE_MATCH:
476 ps->regexp.AddString(s, len);
477 break;
478 case XML_STATE_ID:
479 ps->id.AddString(s, len);
480 break;
481 case XML_STATE_LEVEL:
482 ps->level.AddString(s, len);
483 break;
484 case XML_STATE_SOURCE:
485 ps->source.AddString(s, len);
486 break;
487 case XML_STATE_EVENT:
488 ps->event.AddString(s, len);
489 break;
490 case XML_STATE_FILE:
491 ps->file.AddString(s, len);
492 break;
493 case XML_STATE_CONTEXT:
494 ps->context.AddString(s, len);
495 break;
496 case XML_STATE_DESCRIPTION:
497 ps->description.AddString(s, len);
498 break;
499 case XML_STATE_MACRO:
500 ps->macro.AddString(s, len);
501 break;
502 default:
503 break;
504 }
505}
506
507#endif
508
509BOOL LogParser::CreateFromXML(const char *xml, int xmlLen, char *errorText, int errBufSize)
510{
511#if HAVE_LIBEXPAT
512 XML_Parser parser = XML_ParserCreate(NULL);
513 XML_PARSER_STATE state;
514 BOOL success;
515
516 // Parse XML
517 state.parser = this;
518 state.state = -1;
519 XML_SetUserData(parser, &state);
520 XML_SetElementHandler(parser, StartElement, EndElement);
521 XML_SetCharacterDataHandler(parser, CharData);
522 success = (XML_Parse(parser, xml, (xmlLen == -1) ? strlen(xml) : xmlLen, TRUE) != XML_STATUS_ERROR);
523
524 if (!success && (errorText != NULL))
525 {
526 snprintf(errorText, errBufSize, "%s at line %d",
527 XML_ErrorString(XML_GetErrorCode(parser)),
528 XML_GetCurrentLineNumber(parser));
529 }
530 XML_ParserFree(parser);
531
532 if (success && (state.state == XML_STATE_ERROR))
533 {
534 success = FALSE;
535 if (errorText != NULL)
536 strncpy(errorText, state.errorText, errBufSize);
537 }
538
539 return success;
540#else
541
542 if (errorText != NULL)
543 strncpy(errorText, "Compiled without XML support", errBufSize);
544 return FALSE;
545#endif
546}
547
548
549//
550// Resolve event name
551//
552
553DWORD LogParser::ResolveEventName(const TCHAR *name, DWORD defVal)
554{
555 if (m_eventNameList != NULL)
556 {
557 int i;
558
559 for(i = 0; m_eventNameList[i].text != NULL; i++)
560 if (!_tcsicmp(name, m_eventNameList[i].text))
561 return m_eventNameList[i].code;
562 }
563
564 if (m_eventResolver != NULL)
565 {
566 DWORD val;
567
568 if (m_eventResolver(name, &val))
569 return val;
570 }
571
572 return defVal;
573}