一 说明
suricata的日志先按照packet日志/事务日志/文件日志/流日志进行分类,这个在output.c的OutputRegisterRootLoggers函数中有注册,内容如下:
void OutputRegisterRootLoggers(void)
{
OutputPacketLoggerRegister();
OutputTxLoggerRegister();
OutputFiledataLoggerRegister();
OutputFileLoggerRegister();
OutputStreamingLoggerRegister();
}
以tx的日志为例,看下注册的代码:
void OutputTxLoggerRegister (void)
{
OutputRegisterRootLogger(OutputTxLogThreadInit, OutputTxLogThreadDeinit,
OutputTxLogExitPrintStats, OutputTxLog);
}
将这种大类的日志注册到rootLogger上面如下:
void OutputRegisterRootLogger(ThreadInitFunc ThreadInit,
ThreadDeinitFunc ThreadDeinit,
ThreadExitPrintStatsFunc ThreadExitPrintStats,
OutputLogFunc LogFunc)
{
RootLogger *logger = SCCalloc(1, sizeof(*logger));
if (logger == NULL) {
return;
}
logger->ThreadInit = ThreadInit;
logger->ThreadDeinit = ThreadDeinit;
logger->ThreadExitPrintStats = ThreadExitPrintStats;
logger->LogFunc = LogFunc;
TAILQ_INSERT_TAIL(&RootLoggers, logger, entries);
}
RootLoggers就是全局的大类日志,循环调用每个大类的日志输出内容如下:
TmEcode OutputLoggerLog(ThreadVars *tv, Packet *p, void *thread_data)
{
LoggerThreadStore *thread_store = (LoggerThreadStore *)thread_data;
RootLogger *logger = TAILQ_FIRST(&RootLoggers);
LoggerThreadStoreNode *thread_store_node = TAILQ_FIRST(thread_store);
while (logger && thread_store_node) {
if (logger->LogFunc != NULL) {
logger->LogFunc(tv, p, thread_store_node->thread_data);
}
logger = TAILQ_NEXT(logger, entries);
thread_store_node = TAILQ_NEXT(thread_store_node, entries);
}
return TM_ECODE_OK;
}
那看下PacketLog(这个日志更简单)这个大类的日志输出吧:
static TmEcode OutputPacketLog(ThreadVars *tv, Packet *p, void *thread_data)
{
BUG_ON(thread_data == NULL);
if (list == NULL) {
/* No child loggers. */
return TM_ECODE_OK;
}
OutputLoggerThreadData *op_thread_data = (OutputLoggerThreadData *)thread_data;
OutputPacketLogger *logger = list;
OutputLoggerThreadStore *store = op_thread_data->store;
BUG_ON(logger == NULL && store != NULL);
BUG_ON(logger != NULL && store == NULL);
BUG_ON(logger == NULL && store == NULL);
while (logger && store) {
BUG_ON(logger->LogFunc == NULL || logger->ConditionFunc == NULL);
if ((logger->ConditionFunc(tv, (const Packet *)p)) == TRUE) {
PACKET_PROFILING_LOGGER_START(p, logger->logger_id);
logger->LogFunc(tv, store->thread_data, (const Packet *)p);
PACKET_PROFILING_LOGGER_END(p, logger->logger_id);
}
logger = logger->next;
store = store->next;
BUG_ON(logger == NULL && store != NULL);
BUG_ON(logger != NULL && store == NULL);
}
return TM_ECODE_OK;
}
关键是从list这个静态链表中取日志,然后输出,那么这个静态的链表的值在哪里赋值的那?
我们看下非root类的日志注册:
void OutputRegisterLoggers(void) {
/* custom format log*/
LogCustomFormatRegister();
LuaLogRegister();
/* fast log */
AlertFastLogRegister();
/* debug log */
AlertDebugLogRegister();
/* prelue log */
AlertPreludeRegister();
/* syslog log */
AlertSyslogRegister();
/* unified2 log */
Unified2AlertRegister();
/* drop log */
LogDropLogRegister();
JsonDropLogRegister();
....
}
很多,看下具体一个注册到底干了什么事情?
void JsonDropLogRegister (void)
{
OutputRegisterPacketModule(LOGGER_JSON_DROP, MODULE_NAME, "drop-json-log",
JsonDropLogInitCtx, JsonDropLogger, JsonDropLogCondition,
JsonDropLogThreadInit, JsonDropLogThreadDeinit, NULL);
OutputRegisterPacketSubModule(LOGGER_JSON_DROP, "eve-log", MODULE_NAME,
"eve-log.drop", JsonDropLogInitCtxSub, JsonDropLogger,
JsonDropLogCondition, JsonDropLogThreadInit, JsonDropLogThreadDeinit,
NULL);
}
在进一步看下怎么注册的:
void OutputRegisterPacketModule(LoggerId id, const char *name,
const char *conf_name, OutputInitFunc InitFunc,
PacketLogger PacketLogFunc, PacketLogCondition PacketConditionFunc,
ThreadInitFunc ThreadInit, ThreadDeinitFunc ThreadDeinit,
ThreadExitPrintStatsFunc ThreadExitPrintStats)
{
if (unlikely(PacketLogFunc == NULL || PacketConditionFunc == NULL)) {
goto error;
}
OutputModule *module = SCCalloc(1, sizeof(*module));
if (unlikely(module == NULL)) {
goto error;
}
module->logger_id = id;
module->name = name;
module->conf_name = conf_name;
module->InitFunc = InitFunc;
module->PacketLogFunc = PacketLogFunc;
module->PacketConditionFunc = PacketConditionFunc;
module->ThreadInit = ThreadInit;
module->ThreadDeinit = ThreadDeinit;
module->ThreadExitPrintStats = ThreadExitPrintStats;
TAILQ_INSERT_TAIL(&output_modules, module, entries);
SCLogDebug("Packet logger \"%s\" registered.", name);
return;
error:
SCLogError(SC_ERR_FATAL, "Fatal error encountered. Exiting...");
exit(EXIT_FAILURE);
}
申请一个日志模块,然后加入到全局变量:output_modules 里面。
这个地方有点蒙了,为什么循环用的是list,这里面是日志模块,这两者是怎么关联的。
通过循环获取output_modules内容填充到list中去。runmodes.c里面的函数如下:
static void RunModeInitializeEveOutput(ConfNode *conf, OutputCtx *parent_ctx)
{
ConfNode *types = ConfNodeLookupChild(conf, "types");
SCLogDebug("types %p", types);
if (types == NULL) {
return;
}
ConfNode *type = NULL;
TAILQ_FOREACH(type, &types->head, next) {
SCLogConfig("enabling 'eve-log' module '%s'", type->val);
int sub_count = 0;
char subname[256];
snprintf(subname, sizeof(subname), "eve-log.%s", type->val);
/* Now setup all registers logger of this name. */
OutputModule *sub_module;
TAILQ_FOREACH(sub_module, &output_modules, entries) {
if (strcmp(subname, sub_module->conf_name) == 0) {
sub_count++;
if (sub_module->parent_name == NULL ||
strcmp(sub_module->parent_name, "eve-log") != 0) {
FatalError(SC_ERR_INVALID_ARGUMENT,
"bad parent for %s", subname);
}
if (sub_module->InitSubFunc == NULL) {
FatalError(SC_ERR_INVALID_ARGUMENT,
"bad sub-module for %s", subname);
}
ConfNode *sub_output_config =
ConfNodeLookupChild(type, type->val);
// sub_output_config may be NULL if no config
/* pass on parent output_ctx */
OutputInitResult result =
sub_module->InitSubFunc(sub_output_config, parent_ctx);
if (!result.ok || result.ctx == NULL) {
continue;
}
AddOutputToFreeList(sub_module, result.ctx);
SetupOutput(sub_module->name, sub_module,
result.ctx);
}
}
/* Error is no registered loggers with this name
* were found .*/
if (!sub_count) {
FatalErrorOnInit(SC_ERR_INVALID_ARGUMENT,
"No output module named %s", subname);
continue;
}
}
}
runmodes.c中有个SetupOut函数:
static void SetupOutput(const char *name, OutputModule *module, OutputCtx *output_ctx)
{
/* flow logger doesn't run in the packet path */
if (module->FlowLogFunc) {
OutputRegisterFlowLogger(module->name, module->FlowLogFunc,
output_ctx, module->ThreadInit, module->ThreadDeinit,
module->ThreadExitPrintStats);
return;
}
/* stats logger doesn't run in the packet path */
if (module->StatsLogFunc) {
OutputRegisterStatsLogger(module->name, module->StatsLogFunc,
output_ctx,module->ThreadInit, module->ThreadDeinit,
module->ThreadExitPrintStats);
return;
}
if (module->logger_id == LOGGER_ALERT_DEBUG) {
debuglog_enabled = 1;
}
if (module->PacketLogFunc) {
SCLogDebug("%s is a packet logger", module->name);
OutputRegisterPacketLogger(module->logger_id, module->name,
module->PacketLogFunc, module->PacketConditionFunc, output_ctx,
module->ThreadInit, module->ThreadDeinit,
module->ThreadExitPrintStats);
} else if (module->TxLogFunc) {
SCLogDebug("%s is a tx logger", module->name);
OutputRegisterTxLogger(module->logger_id, module->name, module->alproto,
module->TxLogFunc, output_ctx, module->tc_log_progress,
module->ts_log_progress, module->TxLogCondition,
module->ThreadInit, module->ThreadDeinit,
module->ThreadExitPrintStats);
logger_bits[module->alproto] |= (1<<module->logger_id);
} else if (module->FiledataLogFunc) {
SCLogDebug("%s is a filedata logger", module->name);
OutputRegisterFiledataLogger(module->logger_id, module->name,
module->FiledataLogFunc, output_ctx, module->ThreadInit,
module->ThreadDeinit, module->ThreadExitPrintStats);
filedata_logger_count++;
} else if (module->FileLogFunc) {
SCLogDebug("%s is a file logger", module->name);
OutputRegisterFileLogger(module->logger_id, module->name,
module->FileLogFunc, output_ctx, module->ThreadInit,
module->ThreadDeinit, module->ThreadExitPrintStats);
file_logger_count++;
} else if (module->StreamingLogFunc) {
SCLogDebug("%s is a streaming logger", module->name);
OutputRegisterStreamingLogger(module->logger_id, module->name,
module->StreamingLogFunc, output_ctx, module->stream_type,
module->ThreadInit, module->ThreadDeinit,
module->ThreadExitPrintStats);
} else {
SCLogError(SC_ERR_INVALID_ARGUMENT, "Unknown logger type: name=%s",
module->name);
}
}
二 整理下逻辑
2.1 初始化思路:
1)首先通过rootlogger的注册,注册几个基本的大日志,注册到RootLoggers里面。
注册的循环函数是对一个大类里面的list做循环调用打印日志。
2)通过非rootLogger的日志注册,注册具体的日志到output_modules模块中。
3)在初始化的时候会将output_modules 变成串到相关大类的list里面去。
2.3 调用思路
1)通过flow-worker.c中的OutputLoggerLog 函数调用。
2)上面函数遍历:RootLoggers 调用相关大类的日志打印函数。
3)日志打印函数遍历内部的list循环打印。
4)list的循环其实是注册具体日志的具体日志模块转变得到的大类日志。
5)实际输出日志内容。