/* pmdb2diag.c * * This is a parser module specifically for DB2diag log file. * It extracted program, pid and severity from the log. * * Copyright 2015 Philippe Duveau @ Pari Mutuel Urbain. * * This file is contribution of rsyslog. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * -or- * see COPYING.ASL20 in the source distribution * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "config.h" #include #include #include #include #include #include #ifdef HAVE_SYS_TIME_H # include #endif #include "rsyslog.h" #include "conf.h" #include "syslogd-types.h" #include "template.h" #include "msg.h" #include "module-template.h" #include "glbl.h" #include "errmsg.h" #include "parser.h" #include "datetime.h" #include "unicode-helper.h" MODULE_TYPE_PARSER MODULE_TYPE_NOKEEP PARSER_NAME("db2.diag") MODULE_CNFNAME("pmdb2diag") /* internal structures */ DEF_PMOD_STATIC_DATA DEFobjCurrIf(glbl) DEFobjCurrIf(datetime) /* input instance parameters */ static struct cnfparamdescr parserpdescr[] = { { "levelpos", eCmdHdlrInt, 0 }, { "timepos", eCmdHdlrInt, 0 }, { "timeformat", eCmdHdlrString, 0 }, { "pidstarttoprogstartshift", eCmdHdlrInt, 0 }, }; static struct cnfparamblk parserpblk = { CNFPARAMBLK_VERSION, sizeof(parserpdescr)/sizeof(struct cnfparamdescr), parserpdescr }; struct instanceConf_s { int levelpos; /* expected severity position in read message */ int timepos; /* expected time position in read message */ int pidstarttoprogstartshift; /* position of prog related to pid */ char *timeformat; /* format of timestamp in read message */ char sepSec; /* decimal separator between second and milliseconds */ }; BEGINisCompatibleWithFeature CODESTARTisCompatibleWithFeature if(eFeat == sFEATUREAutomaticPRIParsing) iRet = RS_RET_OK; ENDisCompatibleWithFeature BEGINparse2 struct tm tm; char *ms, *timepos, *pid, *prog, *eprog, *backslash, *end, *lvl; int lprog, lpid, lvl_len; char buffer[128]; CODESTARTparse2 assert(pMsg != NULL); assert(pMsg->pszRawMsg != NULL); DBGPRINTF("Message will now be parsed by \"db2diag\" parser.\n"); if(pMsg->iLenRawMsg - (int)pMsg->offAfterPRI < pInst->levelpos+4) ABORT_FINALIZE(RS_RET_COULD_NOT_PARSE); /* Instead of comparing strings which a waste of cpu cycles we take interpret the 4 first chars of * level read it as int32 and compare it to same interpretation of our constant "levels" * So this test is not sensitive to ENDIANESS. This is not a clean way but very efficient. */ lvl = (char*)(pMsg->pszRawMsg + pMsg->offAfterPRI + pInst->levelpos); switch (*lvl) { case 'C': /* Critical */ pMsg->iSeverity = LOG_EMERG; lvl_len = 8; break; case 'A': /* Alert */ pMsg->iSeverity = LOG_ALERT; lvl_len = 5; break; case 'S': /* Severe */ pMsg->iSeverity = LOG_CRIT; lvl_len = 6; break; case 'E': /* Error / Event */ pMsg->iSeverity = (lvl[1] == 'r') ? LOG_ERR : LOG_NOTICE; lvl_len = 5; break; case 'W': /* Warning */ pMsg->iSeverity = LOG_WARNING; lvl_len = 7; break; case 'I': /* Info */ pMsg->iSeverity = LOG_INFO; lvl_len = 4; break; case 'D': /* Debug */ pMsg->iSeverity = LOG_DEBUG; lvl_len = 5; break; default: /* perhaps the message does not contain a proper level if so don't parse the log */ ABORT_FINALIZE(0); } /* let recheck with the real level len */ if(pMsg->iLenRawMsg - (int)pMsg->offAfterPRI < pInst->levelpos+lvl_len) ABORT_FINALIZE(RS_RET_COULD_NOT_PARSE); DBGPRINTF("db2parse Level %d\n", pMsg->iSeverity); end = (char*)pMsg->pszRawMsg + pMsg->iLenRawMsg ; timepos = (char*)pMsg->pszRawMsg + pMsg->offAfterPRI + pInst->timepos; DBGPRINTF("db2parse Time %.30s\n", timepos); ms = strptime(timepos, pInst->timeformat, &tm); if (ms > timepos && *(ms-1) == pInst->sepSec) { /* the timestamp could be properly interpreted by strptime & our format then set proper timestamp */ int secfrac = 0, tzoff = 0; char *tzpos = strchr(ms, '+'); if (!tzpos) tzpos = strchr(ms, '-'); if (!tzpos) tzpos = (char*)"+"; sscanf(ms, (*tzpos == '+') ? "%d+%d " : "%d-%d ", &secfrac, &tzoff); pMsg->tTIMESTAMP.year = tm.tm_year+1900; pMsg->tTIMESTAMP.month = tm.tm_mon + 1; pMsg->tTIMESTAMP.day = tm.tm_mday; pMsg->tTIMESTAMP.hour = tm.tm_hour; pMsg->tTIMESTAMP.minute = tm.tm_min; pMsg->tTIMESTAMP.second = tm.tm_sec; pMsg->tTIMESTAMP.secfrac = secfrac; pMsg->tTIMESTAMP.secfracPrecision = tzpos-ms; pMsg->tTIMESTAMP.OffsetMode = *tzpos; pMsg->tTIMESTAMP.OffsetHour = tzoff / 60; pMsg->tTIMESTAMP.OffsetMinute = tzoff % 60; } pid = strchr((char*)pMsg->pszRawMsg + pInst->levelpos + lvl_len, ':'); if (!pid || pid>=end) ABORT_FINALIZE(0); pid += 2; lpid = strchr(pid, ' ') - pid; DBGPRINTF("db2parse pid %.*s\n", lpid, pid); /* set the pid */ snprintf(buffer, 128, "%.*s", lpid, pid); MsgSetPROCID(pMsg, buffer); prog = pid + pInst->pidstarttoprogstartshift; /* this offset between start of pid to start of prog */ if (prog>=end) ABORT_FINALIZE(0); eprog = strchr(prog, ' '); /* let find the end of the program */ if (eprog && eprog>=end) ABORT_FINALIZE(0); backslash = strchr(prog, '\\'); /* perhaps program contain an backslash */ if (!backslash || backslash>=end) backslash = end; /* Determine the final length of prog */ lprog = (eprog && eprogtimeformat); ENDfreeParserInst static rsRetVal createInstance(instanceConf_t **ppInst) { instanceConf_t *pInst; DEFiRet; CHKmalloc(pInst = (instanceConf_t *)malloc(sizeof(instanceConf_t))); pInst->timeformat = NULL; pInst->levelpos = 59; pInst->timepos = 0; pInst->pidstarttoprogstartshift = 49; *ppInst = pInst; finalize_it: RETiRet; } BEGINnewParserInst struct cnfparamvals *pvals = NULL; int i; CODESTARTnewParserInst inst = NULL; DBGPRINTF("newParserInst (pmdb2diag)\n"); CHKiRet(createInstance(&inst)); if (lst) { if((pvals = nvlstGetParams(lst, &parserpblk, NULL)) == NULL) { ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS); } if(Debug) { DBGPRINTF("parser param blk in pmdb2diag:\n"); cnfparamsPrint(&parserpblk, pvals); } for(i = 0 ; i < parserpblk.nParams ; ++i) { if(!pvals[i].bUsed) continue; if(!strcmp(parserpblk.descr[i].name, "timeformat")) { inst->timeformat = (char*)es_str2cstr(pvals[i].val.d.estr, NULL); } else if(!strcmp(parserpblk.descr[i].name, "timepos")) { inst->timepos = (int)pvals[i].val.d.n; } else if(!strcmp(parserpblk.descr[i].name, "levelpos")) { inst->levelpos = (int) pvals[i].val.d.n; } else if(!strcmp(parserpblk.descr[i].name, "pidstarttoprogstartshift")) { inst->pidstarttoprogstartshift = (int) pvals[i].val.d.n; } else { DBGPRINTF("pmdb2diag: program error, non-handled " "param '%s'\n", parserpblk.descr[i].name); } } } if (inst->timeformat == NULL) { inst->timeformat = strdup("%Y-%m-%d-%H.%M.%S."); inst->sepSec = '.'; }else inst->sepSec = inst->timeformat[strlen(inst->timeformat)-1]; DBGPRINTF("pmdb2diag: parsing date/time with '%s' at position %d and level at position %d.\n", inst->timeformat, inst->timepos, inst->levelpos); finalize_it: CODE_STD_FINALIZERnewParserInst if(lst != NULL) cnfparamvalsDestruct(pvals, &parserpblk); ENDnewParserInst BEGINmodExit CODESTARTmodExit /* release what we no longer need */ objRelease(glbl, CORE_COMPONENT); objRelease(datetime, CORE_COMPONENT); ENDmodExit BEGINqueryEtryPt CODESTARTqueryEtryPt CODEqueryEtryPt_STD_PMOD2_QUERIES CODEqueryEtryPt_IsCompatibleWithFeature_IF_OMOD_QUERIES ENDqueryEtryPt BEGINmodInit() CODESTARTmodInit *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ CODEmodInit_QueryRegCFSLineHdlr CHKiRet(objUse(glbl, CORE_COMPONENT)); CHKiRet(objUse(datetime, CORE_COMPONENT)); DBGPRINTF("pmdb2diag parser init called, compiled with version %s\n", VERSION); ENDmodInit