java sources synched
[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)
32ec6391
VK
173 {
174 Trace(5, _T(" rule has no context"));
5039dede 175 return m_states[CONTEXT_SET_MANUAL];
32ec6391 176 }
5039dede
AK
177
178 state = m_contexts.Get(rule->GetContext());
179 if (state == NULL)
32ec6391
VK
180 {
181 Trace(5, _T(" context '%s' inactive, rule should be skipped"), rule->GetContext());
5039dede 182 return NULL; // Context inactive, don't use this rule
32ec6391 183 }
5039dede 184
32ec6391
VK
185 if (!_tcscmp(state, m_states[CONTEXT_CLEAR]))
186 {
187 Trace(5, _T(" context '%s' inactive, rule should be skipped"), rule->GetContext());
188 return NULL;
189 }
190 else
191 {
192 Trace(5, _T(" context '%s' active (mode=%s)"), rule->GetContext(), state);
193 return state;
194 }
5039dede
AK
195}
196
197
198//
199// Match log line
200//
201
202BOOL LogParser::MatchLine(const char *line, DWORD objectId)
203{
204 int i;
205 const TCHAR *state;
206 BOOL matched = FALSE;
207
208 Trace(2, _T("Match line: \"%s\""), line);
209 m_recordsProcessed++;
210 for(i = 0; i < m_numRules; i++)
211 {
212 Trace(4, _T("checking rule %d \"%s\""), i + 1, m_rules[i]->GetDescription());
213 if (((state = CheckContext(m_rules[i])) != NULL) && m_rules[i]->Match(line, m_cb, objectId, m_userArg))
214 {
215 Trace(1, _T("rule %d \"%s\" matched"), i + 1, m_rules[i]->GetDescription());
216 if (!matched)
217 m_recordsMatched++;
218
219 // Update context
220 if (m_rules[i]->GetContextToChange() != NULL)
221 {
222 m_contexts.Set(m_rules[i]->GetContextToChange(), m_states[m_rules[i]->GetContextAction()]);
223 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()]);
224 }
225
226 // Set context of this rule to inactive if rule context mode is "automatic reset"
227 if (!_tcscmp(state, m_states[CONTEXT_SET_AUTOMATIC]))
228 {
229 m_contexts.Set(m_rules[i]->GetContext(), m_states[CONTEXT_CLEAR]);
230 Trace(2, _T("rule %d \"%s\": context %s cleared because it was set to automatic reset mode"),
231 i + 1, m_rules[i]->GetDescription(), m_rules[i]->GetContext());
232 }
233 matched = TRUE;
234 if (!m_processAllRules || m_rules[i]->GetBreakFlag())
235 break;
236 }
237 }
238 if (i < m_numRules)
239 Trace(2, _T("processing stopped at rule %d \"%s\"; result = %s"), i + 1,
240 m_rules[i]->GetDescription(), matched ? _T("true") : _T("false"));
241 else
242 Trace(2, _T("Processing stopped at end of rules list; result = %s"), matched ? _T("true") : _T("false"));
243 return matched;
244}
245
246
247//
248// Set associated file name
249//
250
251void LogParser::SetFileName(const TCHAR *name)
252{
253 safe_free(m_fileName);
254 m_fileName = (name != NULL) ? _tcsdup(name) : NULL;
255}
256
257
258//
259// Add macro
260//
261
262void LogParser::AddMacro(const TCHAR *name, const TCHAR *value)
263{
264 m_macros.Set(name, value);
265}
266
267
268//
269// Get macro
270//
271
272const TCHAR *LogParser::GetMacro(const TCHAR *name)
273{
274 const TCHAR *value;
275
276 value = m_macros.Get(name);
277 return CHECK_NULL_EX(value);
278}
279
280
281//
282// Create parser configuration from XML
283//
284
285#if HAVE_LIBEXPAT
286
287static void StartElement(void *userData, const char *name, const char **attrs)
288{
289 XML_PARSER_STATE *ps = (XML_PARSER_STATE *)userData;
290
291 if (!strcmp(name, "parser"))
292 {
293 ps->state = XML_STATE_PARSER;
294 ps->parser->SetProcessAllFlag(XMLGetAttrBoolean(attrs, "processAll", FALSE));
295 ps->parser->SetTraceLevel(XMLGetAttrInt(attrs, "trace", 0));
296 }
297 else if (!strcmp(name, "file"))
298 {
299 ps->state = XML_STATE_FILE;
300 }
301 else if (!strcmp(name, "macros"))
302 {
303 ps->state = XML_STATE_MACROS;
304 }
305 else if (!strcmp(name, "macro"))
306 {
307 const char *name;
308
309 ps->state = XML_STATE_MACRO;
310 name = XMLGetAttr(attrs, "name");
311 ps->macroName = CHECK_NULL_A(name);
312 ps->macro = NULL;
313 }
314 else if (!strcmp(name, "rules"))
315 {
316 ps->state = XML_STATE_RULES;
317 }
318 else if (!strcmp(name, "rule"))
319 {
320 ps->regexp = NULL;
321 ps->event = NULL;
322 ps->context = NULL;
323 ps->contextAction = CONTEXT_SET_AUTOMATIC;
324 ps->description = NULL;
325 ps->ruleContext = XMLGetAttr(attrs, "context");
326 ps->breakFlag = XMLGetAttrBoolean(attrs, "break", FALSE);
327 ps->state = XML_STATE_RULE;
328 }
329 else if (!strcmp(name, "match"))
330 {
331 ps->state = XML_STATE_MATCH;
332 ps->invertedRule = XMLGetAttrBoolean(attrs, "invert", FALSE);
333 }
334 else if (!strcmp(name, "id"))
335 {
336 ps->state = XML_STATE_ID;
337 }
338 else if (!strcmp(name, "level"))
339 {
340 ps->state = XML_STATE_LEVEL;
341 }
342 else if (!strcmp(name, "source"))
343 {
344 ps->state = XML_STATE_SOURCE;
345 }
346 else if (!strcmp(name, "event"))
347 {
348 ps->numEventParams = XMLGetAttrDWORD(attrs, "params", 0);
349 ps->state = XML_STATE_EVENT;
350 }
351 else if (!strcmp(name, "context"))
352 {
353 const char *action;
354
355 ps->state = XML_STATE_CONTEXT;
356
357 action = XMLGetAttr(attrs, "action");
358 if (action == NULL)
359 action = "set";
360
361 if (!strcmp(action, "set"))
362 {
363 const char *mode;
364
365 mode = XMLGetAttr(attrs, "reset");
366 if (mode == NULL)
367 mode = "auto";
368
369 if (!strcmp(mode, "auto"))
370 {
371 ps->contextAction = CONTEXT_SET_AUTOMATIC;
372 }
373 else if (!strcmp(mode, "manual"))
374 {
375 ps->contextAction = CONTEXT_SET_MANUAL;
376 }
377 else
378 {
379 ps->errorText = _T("Invalid context reset mode");
380 ps->state = XML_STATE_ERROR;
381 }
382 }
383 else if (!strcmp(action, "clear"))
384 {
385 ps->contextAction = CONTEXT_CLEAR;
386 }
387 else
388 {
389 ps->errorText = _T("Invalid context action");
390 ps->state = XML_STATE_ERROR;
391 }
392 }
393 else if (!strcmp(name, "description"))
394 {
395 ps->state = XML_STATE_DESCRIPTION;
396 }
397 else
398 {
399 ps->state = XML_STATE_ERROR;
400 }
401}
402
403static void EndElement(void *userData, const char *name)
404{
405 XML_PARSER_STATE *ps = (XML_PARSER_STATE *)userData;
406
407 if (!strcmp(name, "parser"))
408 {
409 ps->state = XML_STATE_END;
410 }
411 else if (!strcmp(name, "file"))
412 {
413 ps->parser->SetFileName(ps->file);
414 ps->state = XML_STATE_PARSER;
415 }
416 else if (!strcmp(name, "macros"))
417 {
418 ps->state = XML_STATE_PARSER;
419 }
420 else if (!strcmp(name, "macro"))
421 {
422 ps->parser->AddMacro(ps->macroName, ps->macro);
423 ps->state = XML_STATE_MACROS;
424 }
425 else if (!strcmp(name, "rules"))
426 {
427 ps->state = XML_STATE_PARSER;
428 }
429 else if (!strcmp(name, "rule"))
430 {
431 DWORD event;
432 char *eptr;
433 LogParserRule *rule;
434
435 ps->event.Strip();
436 event = strtoul(ps->event, &eptr, 0);
437 if (*eptr != 0)
438 event = ps->parser->ResolveEventName(ps->event);
439 rule = new LogParserRule(ps->parser, (const char *)ps->regexp, event, ps->numEventParams);
440 if (!ps->ruleContext.IsEmpty())
441 rule->SetContext(ps->ruleContext);
442 if (!ps->context.IsEmpty())
443 {
444 rule->SetContextToChange(ps->context);
445 rule->SetContextAction(ps->contextAction);
446 }
447 if (!ps->description.IsEmpty())
448 rule->SetDescription(ps->description);
449 rule->SetInverted(ps->invertedRule);
450 rule->SetBreakFlag(ps->breakFlag);
451 ps->parser->AddRule(rule);
452 ps->state = XML_STATE_RULES;
453 }
454 else if (!strcmp(name, "match"))
455 {
456 ps->state = XML_STATE_RULE;
457 }
458 else if (!strcmp(name, "id"))
459 {
460 ps->state = XML_STATE_RULE;
461 }
462 else if (!strcmp(name, "level"))
463 {
464 ps->state = XML_STATE_RULE;
465 }
466 else if (!strcmp(name, "source"))
467 {
468 ps->state = XML_STATE_RULE;
469 }
470 else if (!strcmp(name, "event"))
471 {
472 ps->state = XML_STATE_RULE;
473 }
474 else if (!strcmp(name, "context"))
475 {
476 ps->state = XML_STATE_RULE;
477 }
478 else if (!strcmp(name, "description"))
479 {
480 ps->state = XML_STATE_RULE;
481 }
482}
483
484static void CharData(void *userData, const XML_Char *s, int len)
485{
486 XML_PARSER_STATE *ps = (XML_PARSER_STATE *)userData;
487
488 switch(ps->state)
489 {
490 case XML_STATE_MATCH:
491 ps->regexp.AddString(s, len);
492 break;
493 case XML_STATE_ID:
494 ps->id.AddString(s, len);
495 break;
496 case XML_STATE_LEVEL:
497 ps->level.AddString(s, len);
498 break;
499 case XML_STATE_SOURCE:
500 ps->source.AddString(s, len);
501 break;
502 case XML_STATE_EVENT:
503 ps->event.AddString(s, len);
504 break;
505 case XML_STATE_FILE:
506 ps->file.AddString(s, len);
507 break;
508 case XML_STATE_CONTEXT:
509 ps->context.AddString(s, len);
510 break;
511 case XML_STATE_DESCRIPTION:
512 ps->description.AddString(s, len);
513 break;
514 case XML_STATE_MACRO:
515 ps->macro.AddString(s, len);
516 break;
517 default:
518 break;
519 }
520}
521
522#endif
523
524BOOL LogParser::CreateFromXML(const char *xml, int xmlLen, char *errorText, int errBufSize)
525{
526#if HAVE_LIBEXPAT
527 XML_Parser parser = XML_ParserCreate(NULL);
528 XML_PARSER_STATE state;
529 BOOL success;
530
531 // Parse XML
532 state.parser = this;
533 state.state = -1;
534 XML_SetUserData(parser, &state);
535 XML_SetElementHandler(parser, StartElement, EndElement);
536 XML_SetCharacterDataHandler(parser, CharData);
3973f74b 537 success = (XML_Parse(parser, xml, (xmlLen == -1) ? (int)strlen(xml) : xmlLen, TRUE) != XML_STATUS_ERROR);
5039dede
AK
538
539 if (!success && (errorText != NULL))
540 {
541 snprintf(errorText, errBufSize, "%s at line %d",
542 XML_ErrorString(XML_GetErrorCode(parser)),
543 XML_GetCurrentLineNumber(parser));
544 }
545 XML_ParserFree(parser);
546
547 if (success && (state.state == XML_STATE_ERROR))
548 {
549 success = FALSE;
550 if (errorText != NULL)
551 strncpy(errorText, state.errorText, errBufSize);
552 }
553
554 return success;
555#else
556
557 if (errorText != NULL)
558 strncpy(errorText, "Compiled without XML support", errBufSize);
559 return FALSE;
560#endif
561}
562
563
564//
565// Resolve event name
566//
567
568DWORD LogParser::ResolveEventName(const TCHAR *name, DWORD defVal)
569{
570 if (m_eventNameList != NULL)
571 {
572 int i;
573
574 for(i = 0; m_eventNameList[i].text != NULL; i++)
575 if (!_tcsicmp(name, m_eventNameList[i].text))
576 return m_eventNameList[i].code;
577 }
578
579 if (m_eventResolver != NULL)
580 {
581 DWORD val;
582
583 if (m_eventResolver(name, &val))
584 return val;
585 }
586
587 return defVal;
588}