+/* log.c - NeonServ v5.6
+ * Copyright (C) 2011-2012 Philipp Kreil (pk910)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "log.h"
+#include "ChanNode.h"
+#include "IRCEvents.h"
+#include "ClientSocket.h"
+#include "bots.h"
+#include "IOHandler.h"
+#include "tools.h"
+#include "ConfigParser.h"
+
+#define MAXLOGLEN 1024
+
+static void write_log(const char *module, const int section, const char *line, int len);
+static void load_log_targets();
+static void unload_log_targets();
+static int reload_log_targets(int init);
+
+
+#define LOG_TARGET_TYPE_FILE 1
+#define LOG_TARGET_TYPE_STDOUT 2
+#define LOG_TARGET_TYPE_STDERR 3
+#define LOG_TARGET_TYPE_IRC 4
+
+static const struct {
+ int id;
+ char *name;
+} log_sections[] = {
+ {LOG_INFO, "info"},
+ {LOG_DEBUG, "debug"},
+ {LOG_IRCRAW, "raw"},
+ {LOG_MYSQL, "mysql"},
+ {LOG_OVERRIDE, "override"},
+ {LOG_WARNING, "warning"},
+ {LOG_ERROR, "error"},
+ {LOG_FATAL, "fatal"},
+ {0, NULL}
+};
+
+struct log_target {
+ char *module;
+ int section;
+ int type : 8;
+ union {
+ char *channel;
+ FILE *fptr;
+ } target;
+ struct log_target *next;
+};
+
+static struct log_target *log_targets = NULL;
+
+static char *get_section_name(int section_id) {
+ int i = -1;
+ while(log_sections[++i].id) {
+ if(log_sections[i].id == section_id)
+ return log_sections[i].name;
+ }
+ return NULL;
+}
+
+static int get_section_id(char *section_name) {
+ int i = -1;
+ while(log_sections[++i].id) {
+ if(!stricmp(log_sections[i].name, section_name))
+ return log_sections[i].id;
+ }
+ return 0;
+}
+
+
+void printf_log(const char *module, const int section, const char *text, ...) {
+ va_list arg_list;
+ char logBuf[MAXLOGLEN];
+ int pos;
+ logBuf[0] = '\0';
+ va_start(arg_list, text);
+ pos = vsnprintf(logBuf, MAXLOGLEN - 1, text, arg_list);
+ va_end(arg_list);
+ if (pos < 0 || pos > (MAXLOGLEN - 1)) pos = MAXLOGLEN - 1;
+ logBuf[pos] = '\0';
+ write_log(module, section, logBuf, pos);
+}
+
+static void write_log(const char *module, const int section, const char *line, int len) {
+ if(!log_targets)
+ return;
+ SYNCHRONIZE(log_sync);
+ struct log_target *target;
+ char lineBuf[MAXLOGLEN];
+ char timeBuf[80];
+ int lineBufPos, timeBufPos;
+ time_t rawtime;
+ struct tm *timeinfo;
+ int i, j;
+
+ time(&rawtime);
+ timeinfo = localtime(&rawtime);
+ timeBufPos = strftime(timeBuf, 80, "[%X %x] ", timeinfo);
+
+ lineBufPos = sprintf(lineBuf, "(%s:", module);
+ for(i = 0, j = 0; i < LOG_SECTIONS; i++) {
+ if((section & (1 << i))) {
+ char *section_name = get_section_name((1 << i));
+ if(!section_name)
+ continue;
+ if(j++)
+ lineBuf[lineBufPos++] = ',';
+ lineBufPos += sprintf(lineBuf + lineBufPos, "%s", section_name);
+ }
+ }
+ lineBufPos += sprintf(lineBuf + lineBufPos, ") %s", line);
+ //cut off \n \r
+ while(lineBuf[lineBufPos-1] == '\r' || lineBuf[lineBufPos-1] == '\n') {
+ lineBuf[lineBufPos-1] = '\0';
+ lineBufPos--;
+ }
+
+ j = 0;
+ for(target = log_targets; target; target = target->next) {
+ if(strcmp(target->module, "*") && stricmp(target->module, module))
+ continue;
+ if(!(target->section & section))
+ continue;
+ if(target->type == LOG_TARGET_TYPE_IRC) {
+ if(section == LOG_IRCRAW || (!stricmp(module, "iohandler") && section == LOG_DEBUG))
+ continue; //endless loop ;)
+ struct ChanNode *channel = getChanByName(target->target.channel);
+ struct ClientSocket *client;
+ if(channel && (client = getChannelBot(channel, 0))) {
+ putsock(client, "PRIVMSG %s :%s", channel->name, lineBuf);
+ }
+ } else if(target->type == LOG_TARGET_TYPE_FILE) {
+ fwrite(timeBuf, 1, timeBufPos, target->target.fptr);
+ fwrite(lineBuf, 1, lineBufPos, target->target.fptr);
+ fwrite("\n", 1, 1, target->target.fptr);
+ } else if(target->type == LOG_TARGET_TYPE_STDOUT || target->type == LOG_TARGET_TYPE_STDERR) {
+ if(process_state.daemonized)
+ continue; //block stdout / stderr as daemon
+ fprintf((target->type == LOG_TARGET_TYPE_STDOUT ? stdout : stderr), "%s%s\n", timeBuf, lineBuf);
+ j = 1;
+ }
+ }
+ if((process_state.loglevel & section) && !process_state.daemonized && !j) {
+ fprintf(stdout, "%s%s\n", timeBuf, lineBuf);
+ }
+
+ DESYNCHRONIZE(log_sync);
+}
+
+static void load_log_targets() {
+ if(log_targets)
+ return;
+ char **targetlist = get_all_fieldnames("logs");
+ if(!targetlist) return;
+ int i = 0;
+ char tmp[MAXLEN];
+ struct log_target *ctarget;
+ while(targetlist[i]) {
+ sprintf(tmp, "logs.%s", targetlist[i]);
+ char *type = get_string_field(tmp);
+ char *target = strchr(type, ':');
+ char *module = targetlist[i];
+ char *section = strchr(module, ':');
+ if(!target || !section) {
+ i++;
+ continue;
+ }
+ *target = '\0';
+ target++;
+ *section = '\0';
+ section++;
+ ctarget = malloc(sizeof(*ctarget));
+ if(!stricmp(type, "file")) {
+ ctarget->type = LOG_TARGET_TYPE_FILE;
+ ctarget->target.fptr = fopen(target, "a");
+ if(!ctarget->target.fptr) {
+ free(ctarget);
+ ctarget = NULL;
+ }
+ } else if(!stricmp(type, "std")) {
+ if(!stricmp(target, "out"))
+ ctarget->type = LOG_TARGET_TYPE_STDOUT;
+ else if(!stricmp(target, "err"))
+ ctarget->type = LOG_TARGET_TYPE_STDERR;
+ else {
+ free(ctarget);
+ ctarget = NULL;
+ }
+ } else if(!stricmp(type, "irc")) {
+ if(is_valid_chan(target)) {
+ ctarget->type = LOG_TARGET_TYPE_IRC;
+ ctarget->target.channel = strdup(target);
+ } else {
+ free(ctarget);
+ ctarget = NULL;
+ }
+ } else {
+ free(ctarget);
+ ctarget = NULL;
+ }
+ if(ctarget) {
+ ctarget->module = strdup(module);
+ ctarget->section = 0;
+ char *csection = section;
+ while(1) {
+ char *next_section = strchr(csection, ',');
+ if(next_section)
+ *next_section = '\0';
+ if(!strcmp(csection, "*"))
+ ctarget->section |= LOG_ALL;
+ else
+ ctarget->section |= get_section_id(csection);
+ if(next_section) {
+ *next_section = ',';
+ csection = next_section + 1;
+ } else
+ break;
+ }
+ ctarget->next = log_targets;
+ log_targets = ctarget;
+ }
+ target--;
+ *target = ':';
+ section--;
+ *section = ':';
+ i++;
+ }
+}
+
+static void unload_log_targets() {
+ struct log_target *target, *next_target;
+ for(target = log_targets; target; target = next_target) {
+ next_target = target->next;
+ if(target->type == LOG_TARGET_TYPE_IRC)
+ free(target->target.channel);
+ else if(target->type == LOG_TARGET_TYPE_FILE)
+ fclose(target->target.fptr);
+ free(target);
+ }
+ log_targets = NULL;
+}
+
+static int reload_log_targets(int init) {
+ unload_log_targets();
+ load_log_targets();
+ return 1;
+}
+
+static IOHANDLER_LOG_BACKEND(log_iohandler_backend) {
+ int log_level;
+ switch(type) {
+ case IOLOG_DEBUG:
+ log_level = LOG_DEBUG;
+ break;
+ case IOLOG_WARNING:
+ log_level = LOG_WARNING;
+ break;
+ case IOLOG_ERROR:
+ log_level = LOG_ERROR;
+ break;
+ case IOLOG_FATAL:
+ log_level = LOG_FATAL;
+ break;
+ default:
+ log_level = 0;
+ }
+ write_log("iohandler", log_level, line, strlen(line));
+}
+
+void init_log() {
+ load_log_targets();
+ bind_reload(reload_log_targets, 0);
+ iolog_backend = log_iohandler_backend;
+ printf_log("log", LOG_INFO, "initialized log system.");
+}