optimizations and fixes in log watcher repeat count processing
[public/netxms.git] / src / agent / subagents / logwatch / logwatch.cpp
1 /*
2 ** NetXMS LogWatch subagent
3 ** Copyright (C) 2008-2014 Victor Kirhenshtein
4 **
5 ** This program is free software; you can redistribute it and/or modify
6 ** it under the terms of the GNU General Public License as published by
7 ** the Free Software Foundation; either version 2 of the License, or
8 ** (at your option) any later version.
9 **
10 ** This program is distributed in the hope that it will be useful,
11 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
12 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 ** GNU General Public License for more details.
14 **
15 ** You should have received a copy of the GNU General Public License
16 ** along with this program; if not, write to the Free Software
17 ** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 **
19 ** File: logwatch.cpp
20 **
21 **/
22
23 #include "logwatch.h"
24
25 /**
26 * Shutdown condition
27 */
28 static CONDITION s_shutdownCondition = INVALID_CONDITION_HANDLE;
29
30 /**
31 * Configured parsers
32 */
33 static ObjectArray<LogParser> s_parsers(16, 16, true);
34
35 /**
36 * Offline (missed during agent's downtime) events processing flag
37 */
38 static bool s_processOfflineEvents;
39
40 /**
41 * File parsing thread
42 */
43 THREAD_RESULT THREAD_CALL ParserThreadFile(void *arg)
44 {
45 ((LogParser *)arg)->monitorFile(s_shutdownCondition);
46 return THREAD_OK;
47 }
48
49 #ifdef _WIN32
50
51 /**
52 * Event log parsing thread
53 */
54 THREAD_RESULT THREAD_CALL ParserThreadEventLog(void *arg)
55 {
56 ((LogParser *)arg)->monitorEventLog(s_shutdownCondition, s_processOfflineEvents ? _T("LogWatch") : NULL);
57 return THREAD_OK;
58 }
59
60 #endif
61
62 /**
63 * Get parser statistics
64 */
65 static LONG H_ParserStats(const TCHAR *cmd, const TCHAR *arg, TCHAR *value, AbstractCommSession *session)
66 {
67 TCHAR name[256];
68
69 if (!AgentGetParameterArg(cmd, 1, name, 256))
70 return SYSINFO_RC_UNSUPPORTED;
71
72 LogParser *parser = NULL;
73 for(int i = 0; i < s_parsers.size(); i++)
74 {
75 LogParser *p = s_parsers.get(i);
76 if (!_tcsicmp(p->getName(), name))
77 {
78 parser = p;
79 break;
80 }
81 }
82
83 if (parser == NULL)
84 {
85 AgentWriteDebugLog(8, _T("LogWatch: H_ParserStats: parser with name \"%s\" cannot be found"), name);
86 return SYSINFO_RC_UNSUPPORTED;
87 }
88
89 switch(*arg)
90 {
91 case 'S': // Status
92 ret_string(value, parser->getStatus());
93 break;
94 case 'M': // Matched records
95 ret_int(value, parser->getMatchedRecordsCount());
96 break;
97 case 'P': // Processed records
98 ret_int(value, parser->getProcessedRecordsCount());
99 break;
100 default:
101 return SYSINFO_RC_UNSUPPORTED;
102 }
103 return SYSINFO_RC_SUCCESS;
104 }
105
106 /**
107 * Get list of configured parsers
108 */
109 static LONG H_ParserList(const TCHAR *cmd, const TCHAR *arg, StringList *value, AbstractCommSession *session)
110 {
111 for(int i = 0; i < s_parsers.size(); i++)
112 value->add(s_parsers.get(i)->getName());
113 return SYSINFO_RC_SUCCESS;
114 }
115
116 /**
117 * Called by master agent at unload
118 */
119 static void SubagentShutdown()
120 {
121 if (s_shutdownCondition != INVALID_CONDITION_HANDLE)
122 ConditionSet(s_shutdownCondition);
123
124 for(int i = 0; i < s_parsers.size(); i++)
125 {
126 ThreadJoin(s_parsers.get(i)->getThread());
127 }
128
129 CleanupLogParserLibrary();
130 }
131
132 /**
133 * Callback for matched log records
134 */
135 static void LogParserMatch(UINT32 eventCode, const TCHAR *eventName, const TCHAR *text,
136 const TCHAR *source, UINT32 eventId, UINT32 severity,
137 int cgCount, TCHAR **cgList, UINT32 objectId, int repeatCount,
138 void *userArg)
139 {
140 int count = cgCount + 1;
141 TCHAR eventIdText[16], severityText[16], repeatCountText[16];
142 _sntprintf(repeatCountText, 16, _T("%d"), repeatCount);
143 if (source != NULL)
144 {
145 _sntprintf(eventIdText, 16, _T("%u"), eventId);
146 _sntprintf(severityText, 16, _T("%u"), severity);
147 count += 4;
148 }
149
150 TCHAR **list = (TCHAR **)malloc(sizeof(TCHAR *) * count);
151 int i;
152 for(i = 0; i < cgCount; i++)
153 list[i] = cgList[i];
154
155 if (source != NULL)
156 {
157 list[i++] = (TCHAR *)source;
158 list[i++] = eventIdText;
159 list[i++] = severityText;
160 }
161 list[i++] = repeatCountText;
162
163 AgentSendTrap2(eventCode, eventName, count, list);
164 free(list);
165 }
166
167 /**
168 * Trace callback
169 */
170 static void LogParserTrace(const TCHAR *format, va_list args)
171 {
172 AgentWriteDebugLog2(7, format, args);
173 }
174
175 /**
176 * Add parser from config parameter
177 */
178 static void AddParserFromConfig(const TCHAR *file)
179 {
180 BYTE *xml;
181 UINT32 size;
182 TCHAR error[1024];
183
184 xml = LoadFile(file, &size);
185 if (xml != NULL)
186 {
187 ObjectArray<LogParser> *parsers = LogParser::createFromXml((const char *)xml, size, error, 1024);
188 if (parsers != NULL)
189 {
190 for (int i = 0; i < parsers->size(); i++)
191 {
192 LogParser *parser = parsers->get(i);
193 if (parser->getFileName() != NULL)
194 {
195 parser->setCallback(LogParserMatch);
196 parser->setTraceCallback(LogParserTrace);
197 s_parsers.add(parser);
198 AgentWriteDebugLog(1, _T("LogWatch: registered parser for file %s, trace level set to %d"),
199 parser->getFileName(), parser->getTraceLevel());
200 #ifdef _WIN32
201 AgentWriteDebugLog(7, _T("LogWatch: Process RSS after parser creation is ") INT64_FMT _T(" bytes"), GetProcessRSS());
202 #endif
203 }
204 else
205 {
206 delete parser;
207 AgentWriteLog(EVENTLOG_ERROR_TYPE, _T("LogWatch: Parser configuration %s missing file name to parse (%d)"), file, i);
208 }
209 }
210 }
211 else
212 {
213 delete parsers;
214 AgentWriteLog(EVENTLOG_ERROR_TYPE, _T("LogWatch: Cannot create parser from configuration file %s (%s)"), file, error);
215 }
216 free(xml);
217 }
218 else
219 {
220 AgentWriteLog(EVENTLOG_ERROR_TYPE, _T("LogWatch: Cannot load parser configuration file %s"), file);
221 }
222 }
223
224 /**
225 * Add to logwatch everything inside logwatch policy folder
226 */
227 static void AddLogwatchPolicyFiles()
228 {
229 const TCHAR *dataDir = AgentGetDataDirectory();
230 TCHAR policyFolder[MAX_PATH];
231 TCHAR tail = dataDir[_tcslen(dataDir) - 1];
232 _sntprintf(policyFolder, MAX_PATH, _T("%s%s%s"), dataDir,
233 ((tail != '\\') && (tail != '/')) ? FS_PATH_SEPARATOR : _T(""),
234 LOGPARSER_AP_FOLDER FS_PATH_SEPARATOR);
235
236 AgentWriteDebugLog(1, _T("AddLogwatchPolicyFiles(): Log parser policy directory: %s"), policyFolder);
237
238 _TDIR *dir = _topendir(policyFolder);
239 if (dir != NULL)
240 {
241 struct _tdirent *d;
242 while((d = _treaddir(dir)) != NULL)
243 {
244 if (!_tcscmp(d->d_name, _T(".")) || !_tcscmp(d->d_name, _T("..")))
245 {
246 continue;
247 }
248
249 TCHAR fullName[MAX_PATH];
250 _tcscpy(fullName, policyFolder);
251 _tcscat(fullName, d->d_name);
252
253 NX_STAT_STRUCT st;
254 if (CALL_STAT(fullName, &st) == 0)
255 {
256 if(S_ISREG(st.st_mode))
257 {
258 AddParserFromConfig(fullName);
259 }
260 }
261 }
262 _tclosedir(dir);
263 }
264 }
265
266 /**
267 * Subagent initialization
268 */
269 static BOOL SubagentInit(Config *config)
270 {
271 InitLogParserLibrary();
272
273 s_processOfflineEvents = config->getValueAsBoolean(_T("/LogWatch/ProcessOfflineEvents"), false);
274
275 ConfigEntry *parsers = config->getEntry(_T("/LogWatch/Parser"));
276 if (parsers != NULL)
277 {
278 for(int i = 0; i < parsers->getValueCount(); i++)
279 AddParserFromConfig(parsers->getValue(i));
280 }
281 AddLogwatchPolicyFiles();
282
283 // Create shutdown condition and start parsing threads
284 s_shutdownCondition = ConditionCreate(TRUE);
285 for(int i = 0; i < s_parsers.size(); i++)
286 {
287 LogParser *p = s_parsers.get(i);
288 #ifdef _WIN32
289 if (p->getFileName()[0] == _T('*')) // event log
290 {
291 p->setThread(ThreadCreateEx(ParserThreadEventLog, 0, p));
292 // Seems that simultaneous calls to OpenEventLog() from two or more threads may
293 // cause entire process to hang
294 ThreadSleepMs(200);
295 }
296 else // regular file
297 {
298 p->setThread(ThreadCreateEx(ParserThreadFile, 0, p));
299 }
300 #else
301 p->setThread(ThreadCreateEx(ParserThreadFile, 0, p));
302 #endif
303 }
304
305 return TRUE;
306 }
307
308 /**
309 * Supported parameters
310 */
311 static NETXMS_SUBAGENT_PARAM s_parameters[] =
312 {
313 { _T("LogWatch.Parser.Status(*)"), H_ParserStats, _T("S"), DCI_DT_INT, _T("Parser {instance} status") },
314 { _T("LogWatch.Parser.MatchedRecords(*)"), H_ParserStats, _T("M"), DCI_DT_INT, _T("Number of records matched by parser {instance}") },
315 { _T("LogWatch.Parser.ProcessedRecords(*)"), H_ParserStats, _T("P"), DCI_DT_INT, _T("Number of records processed by parser {instance}") }
316 };
317
318 /**
319 * Supported lists
320 */
321 static NETXMS_SUBAGENT_LIST s_lists[] =
322 {
323 { _T("LogWatch.ParserList"), H_ParserList, NULL }
324 };
325
326 /**
327 * Subagent information
328 */
329 static NETXMS_SUBAGENT_INFO m_info =
330 {
331 NETXMS_SUBAGENT_INFO_MAGIC,
332 _T("LOGWATCH"), NETXMS_VERSION_STRING,
333 SubagentInit, SubagentShutdown, NULL,
334 sizeof(s_parameters) / sizeof(NETXMS_SUBAGENT_PARAM),
335 s_parameters,
336 sizeof(s_lists) / sizeof(NETXMS_SUBAGENT_LIST),
337 s_lists,
338 0, NULL, // tables
339 0, NULL, // actions
340 0, NULL // push parameters
341 };
342
343 /**
344 * Entry point for NetXMS agent
345 */
346 DECLARE_SUBAGENT_ENTRY_POINT(LOGWATCH)
347 {
348 SetLogParserTraceCallback(AgentWriteDebugLog2);
349 *ppInfo = &m_info;
350 return TRUE;
351 }
352
353 #ifdef _WIN32
354
355 /**
356 * DLL entry point
357 */
358 BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
359 {
360 if (dwReason == DLL_PROCESS_ATTACH)
361 DisableThreadLibraryCalls(hInstance);
362 return TRUE;
363 }
364
365 #endif