1 /* log.c - Diagnostic and error logging
2 * Copyright 2000-2004 srvx Development Team
4 * This file is part of srvx.
6 * srvx 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.
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.
16 * You should have received a copy of the GNU General Public License
17 * along with srvx; if not, write to the Free Software Foundation,
18 * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
23 #include "helpfile.h" /* send_message, message_register, etc */
26 struct logDestination;
28 struct logDest_vtable {
29 const char *type_name;
30 struct logDestination* (*open)(const char *args);
31 void (*reopen)(struct logDestination *self);
32 void (*close)(struct logDestination *self);
33 void (*log_audit)(struct logDestination *self, struct log_type *type, struct logEntry *entry);
34 void (*log_replay)(struct logDestination *self, struct log_type *type, int is_write, const char *line);
35 void (*log_module)(struct logDestination *self, struct log_type *type, enum log_severity sev, const char *message);
38 struct logDestination {
39 struct logDest_vtable *vtbl;
44 DECLARE_LIST(logList, struct logDestination*);
48 struct logList logs[LOG_NUM_SEVERITIES];
49 struct logEntry *log_oldest;
50 struct logEntry *log_newest;
51 unsigned int log_count;
53 unsigned int max_count;
55 unsigned int default_set : 1;
58 static const char *log_severity_names[] = {
71 static struct dict *log_dest_types;
72 static struct dict *log_dests;
73 static struct dict *log_types;
74 static struct log_type *log_default;
75 static int log_inited, log_debugged;
77 DEFINE_LIST(logList, struct logDestination*)
78 static void log_format_audit(struct logEntry *entry);
79 static const struct message_entry msgtab[] = {
80 { "MSG_INVALID_FACILITY", "$b%s$b is an invalid log facility." },
81 { "MSG_INVALID_SEVERITY", "$b%s$b is an invalid severity level." },
85 static struct logDestination *
86 log_open(const char *name)
88 struct logDest_vtable *vtbl;
89 struct logDestination *ld;
93 if ((ld = dict_find(log_dests, name, NULL))) {
97 if ((sep = strchr(name, ':'))) {
98 memcpy(type_name, name, sep-name);
99 type_name[sep-name] = 0;
101 strcpy(type_name, name);
103 if (!(vtbl = dict_find(log_dest_types, type_name, NULL))) {
104 log_module(MAIN_LOG, LOG_ERROR, "Invalid log type for log '%s'.", name);
107 if (!(ld = vtbl->open(sep ? sep+1 : 0)))
109 ld->name = strdup(name);
110 dict_insert(log_dests, ld->name, ld);
116 logList_open(struct logList *ll, struct record_data *rd)
118 struct logDestination *ld;
125 if ((ld = log_open(rd->d.qstring)))
126 logList_append(ll, ld);
128 case RECDB_STRING_LIST:
129 for (ii=0; ii<rd->d.slist->used; ++ii) {
130 if ((ld = log_open(rd->d.slist->list[ii])))
131 logList_append(ll, ld);
140 logList_join(struct logList *target, const struct logList *source)
142 unsigned int ii, jj, kk;
147 target->used += source->used;
148 target->size += source->used;
149 target->list = realloc(target->list, target->size * sizeof(target->list[0]));
150 for (ii = 0; ii < source->used; ++ii, ++jj) {
152 for (is_duplicate = 0, kk = 0; kk < jj; kk++) {
153 if (target->list[kk] == source->list[ii]) {
163 target->list[jj] = source->list[ii];
164 target->list[jj]->refcnt++;
169 logList_close(struct logList *ll)
172 for (ii=0; ii<ll->used; ++ii) {
173 if (!--ll->list[ii]->refcnt) {
174 struct logDestination *ld = ll->list[ii];
186 enum log_severity ls;
188 for (it = dict_first(log_types); it; it = iter_next(it)) {
190 for (ls = 0; ls < LOG_NUM_SEVERITIES; ls++) {
191 logList_close(<->logs[ls]);
193 lt->logs[ls].size = 0;
194 lt->logs[ls].used = 0;
195 lt->logs[ls].list = 0;
201 log_type_free_oldest(struct log_type *lt)
203 struct logEntry *next;
207 next = lt->log_oldest->next;
208 free(lt->log_oldest->default_desc);
209 free(lt->log_oldest);
210 lt->log_oldest = next;
215 log_type_free(void *ptr)
217 struct log_type *lt = ptr;
219 while (lt->log_oldest)
220 log_type_free_oldest(lt);
229 dict_delete(log_types);
230 dict_delete(log_dests);
231 dict_delete(log_dest_types);
234 static enum log_severity
235 find_severity(const char *text)
237 enum log_severity ls;
238 for (ls = 0; ls < LOG_NUM_SEVERITIES; ++ls)
239 if (!ircncasecmp(text, log_severity_names[ls], strlen(log_severity_names[ls])))
241 return LOG_NUM_SEVERITIES;
244 /* Log keys are based on syslog.conf syntax:
245 * KEY := LOGSET '.' SEVSET
246 * LOGSET := LOGLIT | LOGLIT ',' LOGSET
247 * LOGLIT := a registered log type
248 * SEVSET := '*' | SEVLIT | '<' SEVLIT | '<=' SEVLIT | '>' SEVLIT | '>=' SEVLIT | SEVLIT ',' SEVSET
249 * SEVLIT := one of log_severity_names
250 * A KEY contains the Cartesian product of the logs in its LOGSET
251 * and the severities in its SEVSET.
255 log_parse_logset(char *buffer, struct string_list *slist)
259 char *cont = strchr(buffer, ',');
262 string_list_append(slist, strdup(buffer));
268 log_parse_sevset(char *buffer, char targets[LOG_NUM_SEVERITIES])
270 memset(targets, 0, LOG_NUM_SEVERITIES);
273 enum log_severity bound;
276 cont = strchr(buffer, ',');
279 if (buffer[0] == '*' && buffer[1] == 0) {
280 for (bound = 0; bound < LOG_NUM_SEVERITIES; bound++) {
281 /* make people explicitly specify replay targets */
282 if (bound != LOG_REPLAY)
285 } else if (buffer[0] == '<') {
286 if (buffer[1] == '=')
287 bound = find_severity(buffer+2) + 1;
289 bound = find_severity(buffer+1);
290 for (first = 1; bound > 0; bound--) {
291 /* make people explicitly specify replay targets */
292 if (bound != LOG_REPLAY || first) {
297 } else if (buffer[0] == '>') {
298 if (buffer[1] == '=')
299 bound = find_severity(buffer+2);
301 bound = find_severity(buffer+1) + 1;
302 for (first = 1; bound < LOG_NUM_SEVERITIES; bound++) {
303 /* make people explicitly specify replay targets */
304 if (bound != LOG_REPLAY || first) {
310 if (buffer[0] == '=')
312 bound = find_severity(buffer);
320 log_parse_cross(const char *buffer, struct string_list *types, char sevset[LOG_NUM_SEVERITIES])
322 char *buffer_copy, *sep;
324 buffer_copy = strdup(buffer);
325 sep = strchr(buffer_copy, '.');
327 log_parse_logset(buffer_copy, types);
328 log_parse_sevset(sep, sevset);
333 log_parse_options(struct log_type *type, struct dict *conf)
336 opt = database_get_data(conf, "max_age", RECDB_QSTRING);
338 type->max_age = ParseInterval(opt);
339 opt = database_get_data(conf, "max_count", RECDB_QSTRING);
341 type->max_count = strtoul(opt, NULL, 10);
347 struct record_data *rd, *rd2;
350 struct log_type *type;
351 enum log_severity sev;
355 dict_delete(log_dests);
357 log_dests = dict_new();
358 dict_set_free_keys(log_dests, free);
360 rd = conf_get_node("logs");
361 if (rd && (rd->type == RECDB_OBJECT)) {
362 for (it = dict_first(rd->d.object); it; it = iter_next(it)) {
363 if ((sep = strchr(iter_key(it), '.'))) {
364 struct logList logList;
365 char sevset[LOG_NUM_SEVERITIES];
366 struct string_list *slist;
368 /* It looks like a <type>.<severity> record. Try to parse it. */
369 slist = alloc_string_list(4);
370 log_parse_cross(iter_key(it), slist, sevset);
372 logList_open(&logList, iter_data(it));
373 for (ii = 0; ii < slist->used; ++ii) {
374 type = log_register_type(slist->list[ii], NULL);
375 for (sev = 0; sev < LOG_NUM_SEVERITIES; ++sev) {
378 logList_join(&type->logs[sev], &logList);
381 logList_close(&logList);
382 free_string_list(slist);
383 } else if ((rd2 = iter_data(it))
384 && (rd2->type == RECDB_OBJECT)
385 && (type = log_register_type(iter_key(it), NULL))) {
386 log_parse_options(type, rd2->d.object);
388 log_module(MAIN_LOG, LOG_ERROR, "Unknown logs subkey '%s'.", iter_key(it));
399 enum log_severity sev;
400 struct logDestination *log_stdout;
401 struct logList target;
403 log_stdout = log_open("std:out");
404 logList_init(&target);
405 logList_append(&target, log_stdout);
407 for (sev = 0; sev < LOG_NUM_SEVERITIES; ++sev)
408 logList_join(&log_default->logs[sev], &target);
410 logList_close(&target);
418 for (it = dict_first(log_dests); it; it = iter_next(it)) {
419 struct logDestination *ld = iter_data(it);
420 ld->vtbl->reopen(ld);
425 log_register_type(const char *name, const char *default_log)
427 struct log_type *type;
428 struct logDestination *dest;
429 enum log_severity sev;
431 if (!(type = dict_find(log_types, name, NULL))) {
432 type = calloc(1, sizeof(*type));
433 type->name = strdup(name);
435 type->max_count = 1024;
436 dict_insert(log_types, type->name, type);
438 if (default_log && !type->default_set) {
439 /* If any severity level was unspecified in the config, use the default. */
441 for (sev = 0; sev < LOG_NUM_SEVERITIES; ++sev) {
442 if (sev == LOG_REPLAY)
443 continue; /* never default LOG_REPLAY */
444 if (!type->logs[sev].size) {
445 logList_init(&type->logs[sev]);
447 if (!(dest = log_open(default_log)))
451 logList_append(&type->logs[sev], dest);
455 type->default_set = 1;
460 /* logging functions */
463 log_audit(struct log_type *type, enum log_severity sev, struct userNode *user, struct userNode *bot, const char *channel_name, unsigned int flags, const char *command)
465 struct logEntry *entry;
466 unsigned int size, ii;
469 /* First make sure severity is appropriate */
470 if ((sev != LOG_COMMAND) && (sev != LOG_OVERRIDE) && (sev != LOG_STAFF)) {
471 log_module(MAIN_LOG, LOG_ERROR, "Illegal audit severity %d", sev);
474 /* Allocate and fill in the log entry */
475 size = sizeof(*entry) + strlen(user->nick) + strlen(command) + 2;
476 if (user->handle_info)
477 size += strlen(user->handle_info->handle) + 1;
479 size += strlen(channel_name) + 1;
480 if (flags & AUDIT_HOSTMASK)
481 size += strlen(user->ident) + strlen(user->hostname) + 2;
482 entry = calloc(1, size);
483 str_next = (char*)(entry + 1);
488 size = strlen(channel_name) + 1;
489 entry->channel_name = memcpy(str_next, channel_name, size);
493 size = strlen(user->nick) + 1;
494 entry->user_nick = memcpy(str_next, user->nick, size);
497 if (user->handle_info) {
498 size = strlen(user->handle_info->handle) + 1;
499 entry->user_account = memcpy(str_next, user->handle_info->handle, size);
502 if (flags & AUDIT_HOSTMASK) {
503 size = sprintf(str_next, "%s@%s", user->ident, user->hostname) + 1;
504 entry->user_hostmask = str_next;
507 entry->user_hostmask = 0;
510 size = strlen(command) + 1;
511 entry->command = memcpy(str_next, command, size);
515 /* fill in the default text for the event */
516 log_format_audit(entry);
518 /* insert into the linked list */
520 entry->prev = type->log_newest;
521 if (type->log_newest)
522 type->log_newest->next = entry;
524 type->log_oldest = entry;
525 type->log_newest = entry;
528 /* remove old elements from the linked list */
529 while (type->log_count > type->max_count)
530 log_type_free_oldest(type);
531 while (type->log_oldest && (type->log_oldest->time + type->max_age < now))
532 log_type_free_oldest(type);
533 if (type->log_oldest)
534 type->log_oldest->prev = 0;
536 type->log_newest = 0;
538 /* call the destination logs */
539 for (ii=0; ii<type->logs[sev].used; ++ii) {
540 struct logDestination *ld = type->logs[sev].list[ii];
541 ld->vtbl->log_audit(ld, type, entry);
543 for (ii=0; ii<log_default->logs[sev].used; ++ii) {
544 struct logDestination *ld = log_default->logs[sev].list[ii];
545 ld->vtbl->log_audit(ld, type, entry);
550 log_replay(struct log_type *type, int is_write, const char *line)
554 for (ii=0; ii<type->logs[LOG_REPLAY].used; ++ii) {
555 struct logDestination *ld = type->logs[LOG_REPLAY].list[ii];
556 ld->vtbl->log_replay(ld, type, is_write, line);
558 for (ii=0; ii<log_default->logs[LOG_REPLAY].used; ++ii) {
559 struct logDestination *ld = log_default->logs[LOG_REPLAY].list[ii];
560 ld->vtbl->log_replay(ld, type, is_write, line);
565 log_module(struct log_type *type, enum log_severity sev, const char *format, ...)
576 if (sev > LOG_FATAL) {
577 log_module(MAIN_LOG, LOG_ERROR, "Illegal log_module severity %d", sev);
580 va_start(args, format);
581 vsnprintf(msgbuf, sizeof(msgbuf), format, args);
584 for (ii=0; ii<type->logs[sev].used; ++ii) {
585 struct logDestination *ld = type->logs[sev].list[ii];
586 ld->vtbl->log_module(ld, type, sev, msgbuf);
588 for (ii=0; ii<log_default->logs[sev].used; ++ii) {
589 struct logDestination *ld = log_default->logs[sev].list[ii];
590 ld->vtbl->log_module(ld, type, sev, msgbuf);
593 /* Special behavior before we start full operation */
594 fprintf(stderr, "%s: %s\n", log_severity_names[sev], msgbuf);
597 if (sev == LOG_FATAL) {
598 assert(0 && "fatal message logged");
603 /* audit log searching */
606 log_discrim_create(struct userNode *service, struct userNode *user, unsigned int argc, char *argv[])
609 struct logSearch *discrim;
611 /* Assume all criteria require arguments. */
614 send_message(user, service, "MSG_MISSING_PARAMS", argv[0]);
618 discrim = malloc(sizeof(struct logSearch));
619 memset(discrim, 0, sizeof(*discrim));
621 discrim->max_time = INT_MAX;
622 discrim->severities = ~0;
624 for (ii=1; ii<argc-1; ii++) {
625 if (!irccasecmp(argv[ii], "bot")) {
626 struct userNode *bot = GetUserH(argv[++ii]);
628 send_message(user, service, "MSG_NICK_UNKNOWN", argv[ii]);
630 } else if (!IsLocal(bot)) {
631 send_message(user, service, "MSG_NOT_A_SERVICE", argv[ii]);
634 discrim->masks.bot = bot;
635 } else if (!irccasecmp(argv[ii], "channel")) {
636 discrim->masks.channel_name = argv[++ii];
637 } else if (!irccasecmp(argv[ii], "nick")) {
638 discrim->masks.user_nick = argv[++ii];
639 } else if (!irccasecmp(argv[ii], "account")) {
640 discrim->masks.user_account = argv[++ii];
641 } else if (!irccasecmp(argv[ii], "hostmask")) {
642 discrim->masks.user_hostmask = argv[++ii];
643 } else if (!irccasecmp(argv[ii], "command")) {
644 discrim->masks.command = argv[++ii];
645 } else if (!irccasecmp(argv[ii], "age")) {
646 const char *cmp = argv[++ii];
649 discrim->min_time = now - ParseInterval(cmp+2);
651 discrim->min_time = now - (ParseInterval(cmp+1) - 1);
652 } else if (cmp[0] == '>') {
654 discrim->max_time = now - ParseInterval(cmp+2);
656 discrim->max_time = now - (ParseInterval(cmp+1) - 1);
658 discrim->min_time = now - ParseInterval(cmp);
660 } else if (!irccasecmp(argv[ii], "limit")) {
661 discrim->limit = strtoul(argv[++ii], NULL, 10);
662 } else if (!irccasecmp(argv[ii], "level")) {
663 char *severity = argv[++ii];
664 discrim->severities = 0;
666 enum log_severity sev = find_severity(severity);
667 if (sev == LOG_NUM_SEVERITIES) {
668 send_message(user, service, "MSG_INVALID_SEVERITY", severity);
671 discrim->severities |= 1 << sev;
672 severity = strchr(severity, ',');
677 } else if (!irccasecmp(argv[ii], "type")) {
678 if (!(discrim->type = dict_find(log_types, argv[++ii], NULL))) {
679 send_message(user, service, "MSG_INVALID_FACILITY", argv[ii]);
683 send_message(user, service, "MSG_INVALID_CRITERIA", argv[ii]);
695 entry_match(struct logSearch *discrim, struct logEntry *entry)
697 if ((entry->time < discrim->min_time)
698 || (entry->time > discrim->max_time)
699 || !(discrim->severities & (1 << entry->slvl))
700 || (discrim->masks.bot && (discrim->masks.bot != entry->bot))
701 /* don't do glob matching, so that !events #a*b does not match #acb */
702 || (discrim->masks.channel_name
703 && (!entry->channel_name
704 || irccasecmp(entry->channel_name, discrim->masks.channel_name)))
705 || (discrim->masks.user_nick
706 && !match_ircglob(entry->user_nick, discrim->masks.user_nick))
707 || (discrim->masks.user_account
708 && (!entry->user_account
709 || !match_ircglob(entry->user_account, discrim->masks.user_account)))
710 || (discrim->masks.user_hostmask
711 && entry->user_hostmask
712 && !match_ircglob(entry->user_hostmask, discrim->masks.user_hostmask))
713 || (discrim->masks.command
714 && !match_ircglob(entry->command, discrim->masks.command))) {
721 log_report_entry(struct logEntry *match, void *extra)
723 struct logReport *rpt = extra;
724 send_message_type(4, rpt->user, rpt->reporter, "%s", match->default_desc);
728 log_entry_search(struct logSearch *discrim, entry_search_func esf, void *data)
730 unsigned int matched = 0;
733 struct logEntry *entry;
735 for (entry = discrim->type->log_oldest;
737 entry = entry->next) {
739 if (entry_match(discrim, entry)) {
741 if (++matched >= discrim->limit)
748 for (it = dict_first(log_types); it; it = iter_next(it)) {
749 discrim->type = iter_data(it);
750 matched += log_entry_search(discrim, esf, data);
757 /* generic helper functions */
760 log_format_timestamp(unsigned long when, struct string_buffer *sbuf)
765 localtime_r(&feh, &local);
766 if (sbuf->size < 24) {
769 sbuf->list = calloc(1, 24);
771 sbuf->used = sprintf(sbuf->list, "[%02d:%02d:%02d %02d/%02d/%04d]", local.tm_hour, local.tm_min, local.tm_sec, local.tm_mon+1, local.tm_mday, local.tm_year+1900);
775 log_format_audit(struct logEntry *entry)
777 struct string_buffer sbuf;
778 memset(&sbuf, 0, sizeof(sbuf));
779 log_format_timestamp(entry->time, &sbuf);
780 string_buffer_append_string(&sbuf, " (");
781 string_buffer_append_string(&sbuf, entry->bot->nick);
782 if (entry->channel_name) {
783 string_buffer_append(&sbuf, ':');
784 string_buffer_append_string(&sbuf, entry->channel_name);
786 string_buffer_append_string(&sbuf, ") [");
787 string_buffer_append_string(&sbuf, entry->user_nick);
788 if (entry->user_hostmask) {
789 string_buffer_append(&sbuf, '!');
790 string_buffer_append_string(&sbuf, entry->user_hostmask);
792 if (entry->user_account) {
793 string_buffer_append(&sbuf, ':');
794 string_buffer_append_string(&sbuf, entry->user_account);
796 string_buffer_append_string(&sbuf, "]: ");
797 string_buffer_append_string(&sbuf, entry->command);
798 entry->default_desc = strdup(sbuf.list);
802 /* shared stub log operations act as a noop */
805 ldNop_reopen(UNUSED_ARG(struct logDestination *self_)) {
806 /* no operation necessary */
810 ldNop_replay(UNUSED_ARG(struct logDestination *self_), UNUSED_ARG(struct log_type *type), UNUSED_ARG(int is_write), UNUSED_ARG(const char *line)) {
811 /* no operation necessary */
816 struct logDest_file {
817 struct logDestination base;
821 static struct logDest_vtable ldFile_vtbl;
823 static struct logDestination *
824 ldFile_open(const char *args) {
825 struct logDest_file *ld;
826 ld = calloc(1, sizeof(*ld));
827 ld->base.vtbl = &ldFile_vtbl;
828 ld->fname = strdup(args);
829 ld->output = fopen(ld->fname, "a");
834 ldFile_reopen(struct logDestination *dest_) {
835 struct logDest_file *dest = (struct logDest_file*)dest_;
836 fclose(dest->output);
837 dest->output = fopen(dest->fname, "a");
841 ldFile_close(struct logDestination *dest_) {
842 struct logDest_file *dest = (struct logDest_file*)dest_;
843 fclose(dest->output);
849 ldFile_audit(struct logDestination *dest_, UNUSED_ARG(struct log_type *type), struct logEntry *entry) {
850 struct logDest_file *dest = (struct logDest_file*)dest_;
851 fputs(entry->default_desc, dest->output);
852 fputc('\n', dest->output);
853 fflush(dest->output);
857 ldFile_replay(struct logDestination *dest_, UNUSED_ARG(struct log_type *type), int is_write, const char *line) {
858 struct logDest_file *dest = (struct logDest_file*)dest_;
859 struct string_buffer sbuf;
860 memset(&sbuf, 0, sizeof(sbuf));
861 log_format_timestamp(now, &sbuf);
862 string_buffer_append_string(&sbuf, is_write ? "W: " : " ");
863 string_buffer_append_string(&sbuf, line);
864 fputs(sbuf.list, dest->output);
865 fputc('\n', dest->output);
867 fflush(dest->output);
871 ldFile_module(struct logDestination *dest_, struct log_type *type, enum log_severity sev, const char *message) {
872 struct logDest_file *dest = (struct logDest_file*)dest_;
873 struct string_buffer sbuf;
874 memset(&sbuf, 0, sizeof(sbuf));
875 log_format_timestamp(now, &sbuf);
876 fprintf(dest->output, "%s (%s:%s) %s\n", sbuf.list, type->name, log_severity_names[sev], message);
878 fflush(dest->output);
881 static struct logDest_vtable ldFile_vtbl = {
893 static struct logDest_vtable ldStd_vtbl;
895 static struct logDestination *
896 ldStd_open(const char *args) {
897 struct logDest_file *ld;
898 ld = calloc(1, sizeof(*ld));
899 ld->base.vtbl = &ldStd_vtbl;
900 ld->fname = strdup(args);
902 /* Print to stderr if given "err" and default to stdout otherwise. */
904 ld->output = fdopen(atoi(args), "a");
905 else if (!strcasecmp(args, "err"))
914 ldStd_close(struct logDestination *dest_) {
915 struct logDest_file *dest = (struct logDest_file*)dest_;
921 ldStd_replay(struct logDestination *dest_, UNUSED_ARG(struct log_type *type), int is_write, const char *line) {
922 struct logDest_file *dest = (struct logDest_file*)dest_;
923 fprintf(dest->output, "%s%s\n", is_write ? "W: " : " ", line);
927 ldStd_module(struct logDestination *dest_, UNUSED_ARG(struct log_type *type), enum log_severity sev, const char *message) {
928 struct logDest_file *dest = (struct logDest_file*)dest_;
929 fprintf(dest->output, "%s: %s\n", log_severity_names[sev], message);
932 static struct logDest_vtable ldStd_vtbl = {
945 struct logDestination base;
948 static struct logDest_vtable ldIrc_vtbl;
950 static struct logDestination *
951 ldIrc_open(const char *args) {
952 struct logDest_irc *ld;
953 ld = calloc(1, sizeof(*ld));
954 ld->base.vtbl = &ldIrc_vtbl;
955 ld->target = strdup(args);
960 ldIrc_close(struct logDestination *dest_) {
961 struct logDest_irc *dest = (struct logDest_irc*)dest_;
967 ldIrc_audit(struct logDestination *dest_, UNUSED_ARG(struct log_type *type), struct logEntry *entry) {
968 struct logDest_irc *dest = (struct logDest_irc*)dest_;
970 if (entry->channel_name) {
971 send_target_message(5, dest->target, entry->bot, "(%s", strchr(strchr(entry->default_desc, ' '), ':')+1);
973 send_target_message(5, dest->target, entry->bot, "%s", strchr(entry->default_desc, ')')+2);
978 ldIrc_module(struct logDestination *dest_, struct log_type *type, enum log_severity sev, const char *message) {
979 struct logDest_irc *dest = (struct logDest_irc*)dest_;
980 extern struct userNode *opserv;
982 send_target_message(5, dest->target, opserv, "%s %s: %s\n", type->name, log_severity_names[sev], message);
985 static struct logDest_vtable ldIrc_vtbl = {
991 ldNop_replay, /* totally ignore this - it would be a recipe for disaster */
998 log_types = dict_new();
999 dict_set_free_keys(log_types, free);
1000 dict_set_free_data(log_types, log_type_free);
1001 log_dest_types = dict_new();
1002 /* register log types */
1003 dict_insert(log_dest_types, ldFile_vtbl.type_name, &ldFile_vtbl);
1004 dict_insert(log_dest_types, ldStd_vtbl.type_name, &ldStd_vtbl);
1005 dict_insert(log_dest_types, ldIrc_vtbl.type_name, &ldIrc_vtbl);
1006 conf_register_reload(log_conf_read);
1007 log_default = log_register_type("*", NULL);
1008 reg_exit_func(cleanup_logs);
1009 message_register_table(msgtab);