1 /* log.c - Diagnostic and error logging
2 * Copyright 2000-2004 srvx Development Team
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version. Important limitations are
8 * listed in the COPYING file that accompanies this software.
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.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, email srvx-maintainers@srvx.net.
21 #include "helpfile.h" /* send_message, message_register, etc */
24 struct logDestination;
26 struct logDest_vtable {
27 const char *type_name;
28 struct logDestination* (*open)(const char *args);
29 void (*reopen)(struct logDestination *self);
30 void (*close)(struct logDestination *self);
31 void (*log_audit)(struct logDestination *self, struct log_type *type, struct logEntry *entry);
32 void (*log_replay)(struct logDestination *self, struct log_type *type, int is_write, const char *line);
33 void (*log_module)(struct logDestination *self, struct log_type *type, enum log_severity sev, const char *message);
36 struct logDestination {
37 struct logDest_vtable *vtbl;
42 DECLARE_LIST(logList, struct logDestination*);
46 struct logList logs[LOG_NUM_SEVERITIES];
47 struct logEntry *log_oldest;
48 struct logEntry *log_newest;
49 unsigned int log_count;
51 unsigned int max_count;
52 unsigned int default_set : 1;
55 static const char *log_severity_names[] = {
68 static struct dict *log_dest_types;
69 static struct dict *log_dests;
70 static struct dict *log_types;
71 static struct log_type *log_default;
72 static int log_inited, log_debugged;
74 DEFINE_LIST(logList, struct logDestination*);
75 static void log_format_audit(struct logEntry *entry);
76 static const struct message_entry msgtab[] = {
77 { "MSG_INVALID_FACILITY", "$b%s$b is an invalid log facility." },
78 { "MSG_INVALID_SEVERITY", "$b%s$b is an invalid severity level." },
82 static struct logDestination *
83 log_open(const char *name)
85 struct logDest_vtable *vtbl;
86 struct logDestination *ld;
89 if ((ld = dict_find(log_dests, name, NULL))) {
93 if ((sep = strchr(name, ':'))) {
94 memcpy(type_name, name, sep-name);
95 type_name[sep-name] = 0;
97 strcpy(type_name, name);
99 if (!(vtbl = dict_find(log_dest_types, type_name, NULL))) {
100 log_module(MAIN_LOG, LOG_ERROR, "Invalid log type for log '%s'.", name);
103 if (!(ld = vtbl->open(sep ? sep+1 : 0))) {
106 ld->name = strdup(name);
107 dict_insert(log_dests, ld->name, ld);
113 logList_open(struct logList *ll, struct record_data *rd)
115 struct logDestination *ld;
123 if ((ld = log_open(rd->d.qstring))) {
124 logList_append(ll, ld);
127 case RECDB_STRING_LIST:
128 for (ii=0; ii<rd->d.slist->used; ++ii) {
129 if ((ld = log_open(rd->d.slist->list[ii]))) {
130 logList_append(ll, ld);
140 logList_join(struct logList *target, const struct logList *source)
142 unsigned int ii, jj, kk;
148 target->used += source->used;
149 target->size += source->used;
150 target->list = realloc(target->list, target->size * sizeof(target->list[0]));
151 for (ii = 0; ii < source->used; ++ii, ++jj) {
153 for (dup = 0, kk = 0; kk < jj; kk++) {
154 if (target->list[kk] == source->list[ii]) {
164 target->list[jj] = source->list[ii];
165 target->list[jj]->refcnt++;
170 logList_close(struct logList *ll)
173 for (ii=0; ii<ll->used; ++ii) {
174 if (!--ll->list[ii]->refcnt) {
175 struct logDestination *ld = ll->list[ii];
187 enum log_severity ls;
189 for (it = dict_first(log_types); it; it = iter_next(it)) {
191 for (ls = 0; ls < LOG_NUM_SEVERITIES; ls++) {
192 logList_close(<->logs[ls]);
194 lt->logs[ls].size = 0;
195 lt->logs[ls].used = 0;
196 lt->logs[ls].list = 0;
202 log_type_free(void *ptr)
204 struct log_type *lt = ptr;
205 struct logEntry *le, *next;
207 for (le = lt->log_oldest; le; le = next) {
209 free(le->default_desc);
220 dict_delete(log_types);
221 dict_delete(log_dests);
222 dict_delete(log_dest_types);
225 static enum log_severity
226 find_severity(const char *text)
228 enum log_severity ls;
229 for (ls = 0; ls < LOG_NUM_SEVERITIES; ++ls)
230 if (!ircncasecmp(text, log_severity_names[ls], strlen(log_severity_names[ls])))
232 return LOG_NUM_SEVERITIES;
235 /* Log keys are based on syslog.conf syntax:
236 * KEY := LOGSET '.' SEVSET
237 * LOGSET := LOGLIT | LOGLIT ',' LOGSET
238 * LOGLIT := a registered log type
239 * SEVSET := '*' | SEVLIT | '<' SEVLIT | '<=' SEVLIT | '>' SEVLIT | '>=' SEVLIT | SEVLIG ',' SEVSET
240 * SEVLIT := one of log_severity_names
241 * A KEY contains the Cartesian product of the logs in its LOGSET
242 * and the severities in its SEVSET.
246 log_parse_logset(char *buffer, struct string_list *slist)
250 char *cont = strchr(buffer, ',');
251 if (cont) *cont++ = 0;
252 string_list_append(slist, strdup(buffer));
258 log_parse_sevset(char *buffer, char targets[LOG_NUM_SEVERITIES])
260 memset(targets, 0, LOG_NUM_SEVERITIES);
263 enum log_severity bound;
266 cont = strchr(buffer, ',');
267 if (cont) *cont++ = 0;
268 if (buffer[0] == '*' && buffer[1] == 0) {
269 for (bound = 0; bound < LOG_NUM_SEVERITIES; bound++) {
270 /* make people explicitly specify replay targets */
271 if (bound != LOG_REPLAY)
274 } else if (buffer[0] == '<') {
275 if (buffer[1] == '=') {
276 bound = find_severity(buffer+2) + 1;
278 bound = find_severity(buffer+1);
280 for (first = 1; bound > 0; bound--) {
281 /* make people explicitly specify replay targets */
282 if (bound != LOG_REPLAY || first) {
287 } else if (buffer[0] == '>') {
288 if (buffer[1] == '=') {
289 bound = find_severity(buffer+2);
291 bound = find_severity(buffer+1) + 1;
293 for (first = 1; bound < LOG_NUM_SEVERITIES; bound++) {
294 /* make people explicitly specify replay targets */
295 if (bound != LOG_REPLAY || first) {
301 bound = find_severity(buffer);
309 log_parse_cross(const char *buffer, struct string_list *types, char sevset[LOG_NUM_SEVERITIES])
313 dup = strdup(buffer);
314 sep = strchr(dup, '.');
316 log_parse_logset(dup, types);
317 log_parse_sevset(sep, sevset);
322 log_parse_options(struct log_type *type, struct dict *conf)
325 opt = database_get_data(conf, "max_age", RECDB_QSTRING);
326 if (opt) type->max_age = ParseInterval(opt);
327 opt = database_get_data(conf, "max_count", RECDB_QSTRING);
328 if (opt) type->max_count = strtoul(opt, NULL, 10);
334 struct record_data *rd, *rd2;
337 struct log_type *type;
338 enum log_severity sev;
342 dict_delete(log_dests);
344 log_dests = dict_new();
345 dict_set_free_keys(log_dests, free);
347 rd = conf_get_node("logs");
348 if (rd && (rd->type == RECDB_OBJECT)) {
349 for (it = dict_first(rd->d.object); it; it = iter_next(it)) {
350 if ((sep = strchr(iter_key(it), '.'))) {
351 struct logList logList;
352 char sevset[LOG_NUM_SEVERITIES];
353 struct string_list *slist;
355 /* It looks like a <type>.<severity> record. Try to parse it. */
356 slist = alloc_string_list(4);
357 log_parse_cross(iter_key(it), slist, sevset);
359 logList_open(&logList, iter_data(it));
360 for (ii = 0; ii < slist->used; ++ii) {
361 type = log_register_type(slist->list[ii], NULL);
362 for (sev = 0; sev < LOG_NUM_SEVERITIES; ++sev) {
363 if (!sevset[sev]) continue;
364 logList_join(&type->logs[sev], &logList);
367 logList_close(&logList);
368 free_string_list(slist);
369 } else if ((rd2 = iter_data(it))
370 && (rd2->type == RECDB_OBJECT)
371 && (type = log_register_type(iter_key(it), NULL))) {
372 log_parse_options(type, rd2->d.object);
374 log_module(MAIN_LOG, LOG_ERROR, "Unknown logs subkey '%s'.", iter_key(it));
386 enum log_severity sev;
387 struct logDestination *log_stdout;
388 struct logList target;
390 log_stdout = log_open("std:out");
391 logList_init(&target);
392 logList_append(&target, log_stdout);
394 for (sev = 0; sev < LOG_NUM_SEVERITIES; ++sev) {
395 logList_join(&log_default->logs[sev], &target);
398 logList_close(&target);
406 for (it = dict_first(log_dests); it; it = iter_next(it)) {
407 struct logDestination *ld = iter_data(it);
408 ld->vtbl->reopen(ld);
413 log_register_type(const char *name, const char *default_log)
415 struct log_type *type;
416 struct logDestination *dest;
417 enum log_severity sev;
419 if (!(type = dict_find(log_types, name, NULL))) {
420 type = calloc(1, sizeof(*type));
421 type->name = strdup(name);
423 type->max_count = 1024;
424 dict_insert(log_types, type->name, type);
426 if (default_log && !type->default_set) {
427 /* If any severity level was unspecified in the config, use the default. */
429 for (sev = 0; sev < LOG_NUM_SEVERITIES; ++sev) {
430 if (sev == LOG_REPLAY) continue; /* never default LOG_REPLAY */
431 if (!type->logs[sev].size) {
432 logList_init(&type->logs[sev]);
434 if (!(dest = log_open(default_log))) break;
437 logList_append(&type->logs[sev], dest);
441 type->default_set = 1;
446 /* logging functions */
449 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)
451 struct logEntry *entry;
452 unsigned int size, ii;
455 /* First make sure severity is appropriate */
456 if ((sev != LOG_COMMAND) && (sev != LOG_OVERRIDE) && (sev != LOG_STAFF)) {
457 log_module(MAIN_LOG, LOG_ERROR, "Illegal audit severity %d", sev);
460 /* Allocate and fill in the log entry */
461 size = sizeof(*entry) + strlen(user->nick) + strlen(command) + 2;
462 if (user->handle_info) {
463 size += strlen(user->handle_info->handle) + 1;
466 size += strlen(channel_name) + 1;
468 if (flags & AUDIT_HOSTMASK) {
469 size += strlen(user->ident) + strlen(user->hostname) + 2;
471 entry = calloc(1, size);
472 str_next = (char*)(entry + 1);
477 size = strlen(channel_name) + 1;
478 entry->channel_name = memcpy(str_next, channel_name, size);
482 size = strlen(user->nick) + 1;
483 entry->user_nick = memcpy(str_next, user->nick, size);
486 if (user->handle_info) {
487 size = strlen(user->handle_info->handle) + 1;
488 entry->user_account = memcpy(str_next, user->handle_info->handle, size);
491 if (flags & AUDIT_HOSTMASK) {
492 size = sprintf(str_next, "%s@%s", user->ident, user->hostname) + 1;
493 entry->user_hostmask = str_next;
496 entry->user_hostmask = 0;
499 size = strlen(command) + 1;
500 entry->command = memcpy(str_next, command, size);
504 /* fill in the default text for the event */
505 log_format_audit(entry);
507 /* insert into the linked list */
509 entry->prev = type->log_newest;
510 if (type->log_newest) {
511 type->log_newest->next = entry;
513 type->log_oldest = entry;
515 type->log_newest = entry;
518 /* remove old elements from the linked list */
519 while (type->log_count > type->max_count) {
520 struct logEntry *next = type->log_oldest->next;
521 free(type->log_oldest->default_desc);
522 free(type->log_oldest);
523 type->log_oldest = next;
526 while (type->log_oldest && (type->log_oldest->time + type->max_age < (unsigned long)now)) {
527 struct logEntry *next = type->log_oldest->next;
528 free(type->log_oldest->default_desc);
529 free(type->log_oldest);
530 type->log_oldest = next;
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, ...)
571 if (sev > LOG_FATAL) {
572 log_module(MAIN_LOG, LOG_ERROR, "Illegal log_module severity %d", sev);
575 va_start(args, format);
576 vsnprintf(msgbuf, sizeof(msgbuf), format, args);
579 for (ii=0; ii<type->logs[sev].used; ++ii) {
580 struct logDestination *ld = type->logs[sev].list[ii];
581 ld->vtbl->log_module(ld, type, sev, msgbuf);
583 for (ii=0; ii<log_default->logs[sev].used; ++ii) {
584 struct logDestination *ld = log_default->logs[sev].list[ii];
585 ld->vtbl->log_module(ld, type, sev, msgbuf);
588 /* Special behavior before we start full operation */
589 fprintf(stderr, "%s: %s\n", log_severity_names[sev], msgbuf);
593 /* audit log searching */
596 log_discrim_create(struct userNode *service, struct userNode *user, unsigned int argc, char *argv[])
599 struct logSearch *discrim;
601 /* Assume all criteria require arguments. */
604 send_message(user, service, "MSG_MISSING_PARAMS", argv[0]);
608 discrim = malloc(sizeof(struct logSearch));
609 memset(discrim, 0, sizeof(*discrim));
611 discrim->max_time = INT_MAX;
612 discrim->severities = ~0;
614 for (ii=1; ii<argc-1; ii++) {
615 if (!irccasecmp(argv[ii], "bot")) {
616 struct userNode *bot = GetUserH(argv[++ii]);
618 send_message(user, service, "MSG_NICK_UNKNOWN", argv[ii]);
620 } else if (!IsLocal(bot)) {
621 send_message(user, service, "MSG_NOT_A_SERVICE", argv[ii]);
624 discrim->masks.bot = bot;
625 } else if (!irccasecmp(argv[ii], "channel")) {
626 discrim->masks.channel_name = argv[++ii];
627 } else if (!irccasecmp(argv[ii], "nick")) {
628 discrim->masks.user_nick = argv[++ii];
629 } else if (!irccasecmp(argv[ii], "account")) {
630 discrim->masks.user_account = argv[++ii];
631 } else if (!irccasecmp(argv[ii], "hostmask")) {
632 discrim->masks.user_hostmask = argv[++ii];
633 } else if (!irccasecmp(argv[ii], "command")) {
634 discrim->masks.command = argv[++ii];
635 } else if (!irccasecmp(argv[ii], "age")) {
636 const char *cmp = argv[++ii];
639 discrim->min_time = now - ParseInterval(cmp+2);
641 discrim->min_time = now - (ParseInterval(cmp+1) - 1);
643 } else if (cmp[0] == '>') {
645 discrim->max_time = now - ParseInterval(cmp+2);
647 discrim->max_time = now - (ParseInterval(cmp+1) - 1);
650 discrim->min_time = now - ParseInterval(cmp+2);
652 } else if (!irccasecmp(argv[ii], "limit")) {
653 discrim->limit = strtoul(argv[++ii], NULL, 10);
654 } else if (!irccasecmp(argv[ii], "level")) {
655 char *severity = argv[++ii];
656 discrim->severities = 0;
658 enum log_severity sev = find_severity(severity);
659 if (sev == LOG_NUM_SEVERITIES) {
660 send_message(user, service, "MSG_INVALID_SEVERITY", severity);
663 discrim->severities |= 1 << sev;
664 severity = strchr(severity, ',');
669 } else if (!irccasecmp(argv[ii], "type")) {
670 if (!(discrim->type = dict_find(log_types, argv[++ii], NULL))) {
671 send_message(user, service, "MSG_INVALID_FACILITY", argv[ii]);
675 send_message(user, service, "MSG_INVALID_CRITERIA", argv[ii]);
687 entry_match(struct logSearch *discrim, struct logEntry *entry)
689 if ((entry->time < discrim->min_time)
690 || (entry->time > discrim->max_time)
691 || !(discrim->severities & (1 << entry->slvl))
692 || (discrim->masks.bot && (discrim->masks.bot != entry->bot))
693 /* don't do glob matching, so that !events #a*b does not match #acb */
694 || (discrim->masks.channel_name
695 && (!entry->channel_name
696 || irccasecmp(entry->channel_name, discrim->masks.channel_name)))
697 || (discrim->masks.user_nick
698 && !match_ircglob(entry->user_nick, discrim->masks.user_nick))
699 || (discrim->masks.user_account
700 && (!entry->user_account
701 || !match_ircglob(entry->user_account, discrim->masks.user_account)))
702 || (discrim->masks.user_hostmask
703 && entry->user_hostmask
704 && !match_ircglob(entry->user_hostmask, discrim->masks.user_hostmask))
705 || (discrim->masks.command
706 && !match_ircglob(entry->command, discrim->masks.command))) {
713 log_report_entry(struct logEntry *match, void *extra)
715 struct logReport *rpt = extra;
716 send_message_type(4, rpt->user, rpt->reporter, "%s", match->default_desc);
720 log_entry_search(struct logSearch *discrim, entry_search_func esf, void *data)
722 unsigned int matched = 0;
725 struct logEntry *entry;
727 for (entry = discrim->type->log_oldest; entry; entry = entry->next) {
728 if (entry_match(discrim, entry)) {
730 if (++matched >= discrim->limit)
737 for (it = dict_first(log_types); it; it = iter_next(it)) {
738 discrim->type = iter_data(it);
739 matched += log_entry_search(discrim, esf, data);
746 /* generic helper functions */
749 log_format_timestamp(time_t when, struct string_buffer *sbuf)
752 localtime_r(&when, &local);
753 if (sbuf->size < 24) {
756 sbuf->list = calloc(1, 24);
758 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);
762 log_format_audit(struct logEntry *entry)
764 struct string_buffer sbuf;
765 memset(&sbuf, 0, sizeof(sbuf));
766 log_format_timestamp(entry->time, &sbuf);
767 string_buffer_append_string(&sbuf, " (");
768 string_buffer_append_string(&sbuf, entry->bot->nick);
769 if (entry->channel_name) {
770 string_buffer_append(&sbuf, ':');
771 string_buffer_append_string(&sbuf, entry->channel_name);
773 string_buffer_append_string(&sbuf, ") [");
774 string_buffer_append_string(&sbuf, entry->user_nick);
775 if (entry->user_hostmask) {
776 string_buffer_append(&sbuf, '!');
777 string_buffer_append_string(&sbuf, entry->user_hostmask);
779 if (entry->user_account) {
780 string_buffer_append(&sbuf, ':');
781 string_buffer_append_string(&sbuf, entry->user_account);
783 string_buffer_append_string(&sbuf, "]: ");
784 string_buffer_append_string(&sbuf, entry->command);
785 entry->default_desc = strdup(sbuf.list);
789 /* shared stub log operations act as a noop */
792 ldNop_reopen(UNUSED_ARG(struct logDestination *self_)) {
793 /* no operation necessary */
797 ldNop_replay(UNUSED_ARG(struct logDestination *self_), UNUSED_ARG(struct log_type *type), UNUSED_ARG(int is_write), UNUSED_ARG(const char *line)) {
798 /* no operation necessary */
803 struct logDest_file {
804 struct logDestination base;
808 static struct logDest_vtable ldFile_vtbl;
810 static struct logDestination *
811 ldFile_open(const char *args) {
812 struct logDest_file *ld;
813 ld = calloc(1, sizeof(*ld));
814 ld->base.vtbl = &ldFile_vtbl;
815 ld->fname = strdup(args);
816 ld->output = fopen(ld->fname, "a");
821 ldFile_reopen(struct logDestination *self_) {
822 struct logDest_file *self = (struct logDest_file*)self_;
823 fclose(self->output);
824 self->output = fopen(self->fname, "a");
828 ldFile_close(struct logDestination *self_) {
829 struct logDest_file *self = (struct logDest_file*)self_;
830 fclose(self->output);
836 ldFile_audit(struct logDestination *self_, UNUSED_ARG(struct log_type *type), struct logEntry *entry) {
837 struct logDest_file *self = (struct logDest_file*)self_;
838 fputs(entry->default_desc, self->output);
839 fputc('\n', self->output);
840 fflush(self->output);
844 ldFile_replay(struct logDestination *self_, UNUSED_ARG(struct log_type *type), int is_write, const char *line) {
845 struct logDest_file *self = (struct logDest_file*)self_;
846 struct string_buffer sbuf;
847 memset(&sbuf, 0, sizeof(sbuf));
848 log_format_timestamp(now, &sbuf);
849 string_buffer_append_string(&sbuf, is_write ? "W: " : " ");
850 string_buffer_append_string(&sbuf, line);
851 fputs(sbuf.list, self->output);
852 fputc('\n', self->output);
854 fflush(self->output);
858 ldFile_module(struct logDestination *self_, struct log_type *type, enum log_severity sev, const char *message) {
859 struct logDest_file *self = (struct logDest_file*)self_;
860 struct string_buffer sbuf;
861 memset(&sbuf, 0, sizeof(sbuf));
862 log_format_timestamp(now, &sbuf);
863 fprintf(self->output, "%s (%s:%s) %s\n", sbuf.list, type->name, log_severity_names[sev], message);
865 fflush(self->output);
868 static struct logDest_vtable ldFile_vtbl = {
880 static struct logDest_vtable ldStd_vtbl;
882 static struct logDestination *
883 ldStd_open(const char *args) {
884 struct logDest_file *ld;
885 ld = calloc(1, sizeof(*ld));
886 ld->base.vtbl = &ldStd_vtbl;
887 ld->fname = strdup(args);
889 /* Print to stderr if given "err" and default to stdout otherwise. */
891 ld->output = fdopen(atoi(args), "a");
892 } else if (!strcasecmp(args, "err")) {
902 ldStd_close(struct logDestination *self_) {
903 struct logDest_file *self = (struct logDest_file*)self_;
909 ldStd_replay(struct logDestination *self_, UNUSED_ARG(struct log_type *type), int is_write, const char *line) {
910 struct logDest_file *self = (struct logDest_file*)self_;
911 fprintf(self->output, "%s%s\n", is_write ? "W: " : " ", line);
915 ldStd_module(struct logDestination *self_, UNUSED_ARG(struct log_type *type), enum log_severity sev, const char *message) {
916 struct logDest_file *self = (struct logDest_file*)self_;
917 fprintf(self->output, "%s: %s\n", log_severity_names[sev], message);
920 static struct logDest_vtable ldStd_vtbl = {
933 struct logDestination base;
936 static struct logDest_vtable ldIrc_vtbl;
938 static struct logDestination *
939 ldIrc_open(const char *args) {
940 struct logDest_irc *ld;
941 ld = calloc(1, sizeof(*ld));
942 ld->base.vtbl = &ldIrc_vtbl;
943 ld->target = strdup(args);
948 ldIrc_close(struct logDestination *self_) {
949 struct logDest_irc *self = (struct logDest_irc*)self_;
955 ldIrc_audit(struct logDestination *self_, UNUSED_ARG(struct log_type *type), struct logEntry *entry) {
956 struct logDest_irc *self = (struct logDest_irc*)self_;
958 if (entry->channel_name) {
959 send_target_message(4, self->target, entry->bot, "(%s", strchr(strchr(entry->default_desc, ' '), ':')+1);
961 send_target_message(4, self->target, entry->bot, "%s", strchr(entry->default_desc, ')')+2);
966 ldIrc_module(struct logDestination *self_, struct log_type *type, enum log_severity sev, const char *message) {
967 struct logDest_irc *self = (struct logDest_irc*)self_;
968 extern struct userNode *opserv;
970 send_target_message(4, self->target, opserv, "%s %s: %s\n", type->name, log_severity_names[sev], message);
973 static struct logDest_vtable ldIrc_vtbl = {
979 ldNop_replay, /* totally ignore this - it would be a recipe for disaster */
986 log_types = dict_new();
987 dict_set_free_keys(log_types, free);
988 dict_set_free_data(log_types, log_type_free);
989 log_dest_types = dict_new();
990 /* register log types */
991 dict_insert(log_dest_types, ldFile_vtbl.type_name, &ldFile_vtbl);
992 dict_insert(log_dest_types, ldStd_vtbl.type_name, &ldStd_vtbl);
993 dict_insert(log_dest_types, ldIrc_vtbl.type_name, &ldIrc_vtbl);
994 conf_register_reload(log_conf_read);
995 log_default = log_register_type("*", NULL);
996 reg_exit_func(cleanup_logs);
997 message_register_table(msgtab);