/* ARISA - logging functions * Copyright (C) 2003, 2004 Carl Ritson * * 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 2 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, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA */ #include "arisa.h" #include #include static pthread_key_t title_key; static pthread_once_t title_key_once = PTHREAD_ONCE_INIT; static const struct { const char *mark; const char *colour; } log_colours[] = { { L_ERR, COLOUR_LRED }, { L_INF, COLOUR_YELLOW }, { L_ADM, COLOUR_LYELLOW }, { L_XDC, COLOUR_GREEN }, { L_INT, COLOUR_CYAN }, { L_IRC, COLOUR_LCYAN }, { NULL } }; static size_t log_line(char *buffer, size_t bufsize, const char *src); /** arisa_log * The core arisa logging function, takes a format and associated * arguments and prints it into the bots log file and any other listening * threads message queues. */ void arisa_log(const char *format, ...) { char buffer[524],logmsg[512], *p; time_t now = time(NULL); va_list va; size_t len; int i,j; /* Something important to note here, we embed a timestamp in the 4 byte following the null terminator of the log message. */ if(global == NULL) { va_start(va,format); vfprintf(stderr,format,va); va_end(va); fprintf(stderr,"\n"); return; } strncpy(buffer,"%n",2); va_start(va, format); len = IMIN( vsnprintf(logmsg,511,format,va), 510 ); va_end(va); len = quote_colours(buffer+2,512,logmsg); if(strlen(buffer+2) > 0) { memcpy(buffer+1+2+len,&now,sizeof(time_t)); len += 2 + 1 + sizeof(time_t); for(j = 0; log_colours[j].mark != NULL; ++j) { if(strncmp(log_colours[j].mark,buffer+2, strlen(log_colours[j].mark)) == 0) { strncpy(buffer,log_colours[j].colour,2); break; } } LOCKR(&(global->log_subs.lock)); for(i = 0; i < global->log_subs.no_subs; ++i) { p = xalloc(len); memcpy(p,buffer,len); pqueue_push_back(global->log_subs.subs[i],p); } UNLOCKR(&(global->log_subs.lock)); if(global->log_subs.fd != -1) { char buffer2[524]; len = log_line(buffer2,sizeof(buffer2),buffer); buffer2[len] = '\n'; write(global->log_subs.fd,buffer2,len+1); } } } /** title_free * Internal function used by pthreads to free a threads title. */ static void title_free(void *title) { if(title != NULL) xfree(title); } /** title_key_alloc * Internal function used by pthreads to allocate a title key. */ static void title_key_alloc(void) { pthread_key_create(&title_key,title_free); } /** log_title * Returns a pointer to the title string of the calling thread, * or "" if one is not set. */ const char *log_title(void) { const char *title = (const char *)pthread_getspecific(title_key); if(title != NULL) return title; else return ""; } /** log_set_title * Sets the title string of the calling thread, based on a format and * associated arguments. */ void log_set_title(const char *fmt, ...) { char *buffer; va_list va; pthread_once(&title_key_once,title_key_alloc); buffer = pthread_getspecific(title_key); if(buffer == NULL) { buffer = xalloc(128); pthread_setspecific(title_key,buffer); } va_start(va,fmt); vsnprintf(buffer,128,fmt,va); va_end(va); } /** log_subscribe * Attaches a pointer queue to the list of logging message queues. * New log messages will be xstrdup'd onto the pointer queue as they * occur. * Duplicate calls are safe. */ void log_subscribe(pqueue_t *q) { LOCKR(&(global->log_subs.lock)); PTRARR_ADD(&(global->log_subs.subs),&(global->log_subs.no_subs),q); UNLOCKR(&(global->log_subs.lock)); } /** log_unsubscribe * Removes a pointer queue from the list of logging message queues. * Calls for queues not on the list are safe. */ void log_unsubscribe(pqueue_t *q) { LOCKR(&(global->log_subs.lock)); PTRARR_DEL(&(global->log_subs.subs),&(global->log_subs.no_subs),q); UNLOCKR(&(global->log_subs.lock)); } /** log_open * Returns a FILE type handle to the bot log file, in append mode, * or NULL if the file is not set or cannot be openned. */ static FILE *log_open(const char *fn) { FILE *fh = NULL; if(fn != NULL) fh = fopen(fn,"a"); return fh; } /** log_close * Closes a FILE type hanlde to the bot log file. */ static void log_close(FILE *fh) { fclose(fh); } /** log_line * Used by the log thread to convert a line from the log pointer queue * to a line suitable for writing to the bot log file. The main function * performed is extracting the timestamp and converting it to a textual * form. */ static size_t log_line(char *buffer, size_t bufsize, const char *src) { char ctime_buf[32]; int len; lctime_r((time_t *)(src+strlen(src)+1),ctime_buf,sizeof(ctime_buf)); len = strlen(ctime_buf); if(ctime_buf[len-1] == '\n') { ctime_buf[len-1] = '\0'; len -= 1; } len = IMIN( snprintf(buffer,bufsize,"[%s] ",ctime_buf), bufsize-1 ); return len + colourise(COLOUR_NONE,buffer + len,bufsize - len,src); } static void log_flush(pqueue_t *logq) { struct tm timeval; char buffer[2048],format[2048],filename[2048]; char *p; FILE *fh = NULL; time_t occured; LOCK(global->settings); if(global->settings->log != NULL) xstrncpy(format,global->settings->log,sizeof(format)); else strcpy(format,""); UNLOCK(global->settings); strcpy(filename,""); if(strlen(format) == 0) return; while((p = pqueue_pop_front(logq)) != NULL) { /* Open log if not open */ memcpy(&occured,p+strlen(p)+1,sizeof(time_t)); localtime_r(&occured,&timeval); strftime(buffer,sizeof(buffer),format,&timeval); if(strcmp(buffer,filename) != 0) { if(fh != NULL) log_close(fh); xstrncpy(filename,buffer,sizeof(filename)); fh = log_open(filename); } if(fh == NULL) { pqueue_push_front(logq,p); break; } /* Process Message */ log_line(buffer,sizeof(buffer),p); xfree(p); /* Write to Log */ fprintf(fh,"%s\n",buffer); } if(fh != NULL) log_close(fh); } void *log_thread(void *arg) { pqueue_t *logq; int sleepfor = 0; thread_signal_started(&(global->log_thread)); logq = pqueue_init(NULL,1); log_subscribe(logq); LOG_TITLE("Log Thread"); LOGTP(L_INF,"Started, %s",ARISA_VERSION); while(!thread_should_end()) { if(pqueue_length(logq) > 0) log_flush(logq); LOCK(global->settings); sleepfor = global->settings->log_interval; UNLOCK(global->settings); if(sleepfor <= 0) sleepfor = 60; thread_sleep(sleepfor); } LOGTP(L_INF,"Shutdown"); log_unsubscribe(logq); log_flush(logq); pqueue_free(logq,xfree); thread_signal_finished(); return NULL; }