Index: cdr/cdr_pgsql.c =================================================================== RCS file: /usr/cvsroot/asterisk/cdr/cdr_pgsql.c,v retrieving revision 1.19 diff -u -r1.19 cdr_pgsql.c --- cdr/cdr_pgsql.c 6 Jun 2005 22:12:18 -0000 1.19 +++ cdr/cdr_pgsql.c 5 Aug 2005 19:55:41 -0000 @@ -9,12 +9,15 @@ * Modified September 2003 * Matthew D. Hardeman * + * Modified August 2005 + * Corey Frang + * Added a "spool file" to log insert commands that otherwise would be dropped. + * * This program is free software, distributed under the terms of * the GNU General Public License. * */ -#include #include #include @@ -22,11 +25,13 @@ #include #include +#include +#include + #include #include "asterisk.h" - -ASTERISK_FILE_VERSION(__FILE__, "$Revision: 1.19 $") +ASTERISK_FILE_VERSION(__FILE__, "$Revision: 1.19+c $") #include "asterisk/config.h" #include "asterisk/options.h" @@ -34,22 +39,113 @@ #include "asterisk/cdr.h" #include "asterisk/module.h" #include "asterisk/logger.h" -#include "asterisk.h" +#include "asterisk/utils.h" #define DATE_FORMAT "%Y-%m-%d %T" static char *desc = "PostgreSQL CDR Backend"; static char *name = "pgsql"; static char *config = "cdr_pgsql.conf"; -static char *pghostname = NULL, *pgdbname = NULL, *pgdbuser = NULL, *pgpassword = NULL, *pgdbsock = NULL, *pgdbport = NULL, *table = NULL; -static int hostname_alloc = 0, dbname_alloc = 0, dbuser_alloc = 0, password_alloc = 0, dbsock_alloc = 0, dbport_alloc = 0, table_alloc = 0; +static char *pghostname = NULL, *pgdbname = NULL, *pgdbuser = NULL, *pgpassword = NULL, *pgdbsock = NULL, *pgdbport = NULL, *table = NULL, *pgspool = NULL; +static int hostname_alloc = 0, dbname_alloc = 0, dbuser_alloc = 0, password_alloc = 0, dbsock_alloc = 0, dbport_alloc = 0, table_alloc = 0, pgspool_alloc = 0; static int connected = 0; +static FILE *mf = NULL; + +extern int errno; + AST_MUTEX_DEFINE_STATIC(pgsql_lock); PGconn *conn; PGresult *result; +static int pgunspool(void) +{ + struct stat st; + char buf[2048] = ""; + int error = 0, good=0; + char *pgspoolbackup = NULL; + FILE *back = NULL; + char *pgerror; + + if (!pgspool || ast_strlen_zero(pgspool)) + return 0; + + if (0 == stat(pgspool, &st)) + { + ast_log(LOG_WARNING, "Loading cached records from %s\n", pgspool); + mf = fopen(pgspool, "r"); + if (!mf) + ast_log(LOG_ERROR, "Unable to open %s (%s)\n", pgspool, strerror(errno)); + if (mf) + { + while( fgets(buf, 2048, mf) ) + { + if (!error && PQstatus(conn) == CONNECTION_OK) { + result = PQexec(conn, buf); + if ( PQresultStatus(result) != PGRES_COMMAND_OK) { + pgerror = PQerrorMessage(conn); + ast_log(LOG_ERROR,"SQL FAILED: %s\n", pgerror); + error++; + } else { + good++; + } + } else { + error++; + } + if (error && !pgspoolbackup) + { + pgspoolbackup = malloc(strlen(pgspool) + 5); + if (!pgspoolbackup) + { + ast_log(LOG_ERROR, "Fatal error in cache load, out of memory\n"); + return -1; + } + sprintf(pgspoolbackup, "%s.bak", pgspool); + back = fopen(pgspoolbackup, "a"); + if (!back) + { + ast_log(LOG_ERROR, "Fatal error in cache load, couldn't open %s\n", pgspoolbackup); + return -1; + } + } + if (back) + { + fprintf(back, "%s", buf); + } + } + fclose(mf); + unlink(pgspool); + if (back) + { + fclose(back); + rename(pgspoolbackup, pgspool); + free(pgspoolbackup); + } + ast_log(LOG_WARNING, "finished loading from cache: %d commands executed succesfully, %d failed and left\n", good, error); + } + } + return 0; +} + +static int pgspool_cmd(char *cmd) +{ + if (!pgspool || ast_strlen_zero(pgspool)) + return 0; + + // try to open file each time + mf = fopen(pgspool, "a"); + if (!mf) + ast_log(LOG_ERROR, "Unable to open %s (%s)\n", pgspool, strerror(errno)); + if (mf) { + fprintf(mf, "%s\n", cmd); + fflush(mf); + fclose(mf); + mf = NULL; + } + return 0; +} + static int pgsql_log(struct ast_cdr *cdr) { struct tm tm; @@ -65,14 +161,18 @@ conn = PQsetdbLogin(pghostname, pgdbport, NULL, NULL, pgdbname, pgdbuser, pgpassword); if (PQstatus(conn) != CONNECTION_BAD) { connected = 1; + pgunspool(); } else { pgerror = PQerrorMessage(conn); - ast_log(LOG_ERROR, "cdr_pgsql: Unable to connect to database server %s. Calls will not be logged!\n", pghostname); - ast_log(LOG_ERROR, "cdr_pgsql: Reason: %s\n", pgerror); + if (pgspool) + ast_log(LOG_ERROR, "Unable to connect to database server %s. Spooling Insert Commands to %s!!\n", pghostname, pgspool); + else + ast_log(LOG_ERROR, "Unable to connect to database server %s. Calls will not be logged!\n", pghostname); + ast_log(LOG_ERROR, "Reason: %s\n", pgerror); } } - if (connected) { + if (connected || pgspool) { char *clid=NULL, *dcontext=NULL, *channel=NULL, *dstchannel=NULL, *lastapp=NULL, *lastdata=NULL; char *uniqueid=NULL, *userfield=NULL; @@ -96,12 +196,12 @@ /* Check for all alloca failures above at once */ if ((!clid) || (!dcontext) || (!channel) || (!dstchannel) || (!lastapp) || (!lastdata) || (!uniqueid) || (!userfield)) { - ast_log(LOG_ERROR, "cdr_pgsql: Out of memory error (insert fails)\n"); + ast_log(LOG_ERROR, "Out of memory error (insert fails)\n"); ast_mutex_unlock(&pgsql_lock); return -1; } - ast_log(LOG_DEBUG,"cdr_pgsql: inserting a CDR record.\n"); + ast_log(LOG_DEBUG,"inserting a CDR record.\n"); snprintf(sqlcmd,sizeof(sqlcmd),"INSERT INTO %s (calldate,clid,src,dst,dcontext,channel,dstchannel," "lastapp,lastdata,duration,billsec,disposition,amaflags,accountcode,uniqueid,userfield) VALUES" @@ -109,7 +209,7 @@ table,timestr,clid,cdr->src, cdr->dst, dcontext,channel, dstchannel, lastapp, lastdata, cdr->duration,cdr->billsec,ast_cdr_disp2str(cdr->disposition),cdr->amaflags, cdr->accountcode, uniqueid, userfield); - ast_log(LOG_DEBUG,"cdr_pgsql: SQL command executed: %s\n",sqlcmd); + ast_log(LOG_DEBUG,"SQL command executed: %s\n",sqlcmd); /* Test to be sure we're still connected... */ /* If we're connected, and connection is working, good. */ @@ -117,15 +217,20 @@ if (PQstatus(conn) == CONNECTION_OK) { connected = 1; } else { - ast_log(LOG_ERROR, "cdr_pgsql: Connection was lost... attempting to reconnect.\n"); + ast_log(LOG_ERROR, "Connection was lost... attempting to reconnect.\n"); PQreset(conn); if (PQstatus(conn) == CONNECTION_OK) { - ast_log(LOG_ERROR, "cdr_pgsql: Connection reestablished.\n"); + ast_log(LOG_ERROR, "Connection reestablished.\n"); + pgunspool(); connected = 1; } else { pgerror = PQerrorMessage(conn); - ast_log(LOG_ERROR, "cdr_pgsql: Unable to reconnect to database server %s. Calls will not be logged!\n", pghostname); - ast_log(LOG_ERROR, "cdr_pgsql: Reason: %s\n", pgerror); + if (pgspool) + ast_log(LOG_ERROR, "Unable to connect to database server %s. Spooling Insert Commands to %s!!\n", pghostname, pgspool); + else + ast_log(LOG_ERROR, "Unable to reconnect to database server %s. Calls will not be logged!\n", pghostname); + ast_log(LOG_ERROR, "Reason: %s\n", pgerror); + pgspool_cmd(sqlcmd); connected = 0; ast_mutex_unlock(&pgsql_lock); return -1; @@ -134,20 +239,27 @@ result = PQexec(conn, sqlcmd); if ( PQresultStatus(result) != PGRES_COMMAND_OK) { pgerror = PQresultErrorMessage(result); - ast_log(LOG_ERROR,"cdr_pgsql: Failed to insert call detail record into database!\n"); - ast_log(LOG_ERROR,"cdr_pgsql: Reason: %s\n", pgerror); - ast_log(LOG_ERROR,"cdr_pgsql: Connection may have been lost... attempting to reconnect.\n"); + ast_log(LOG_ERROR,"Failed to insert call detail record into database!\n"); + ast_log(LOG_ERROR,"Reason: %s\n", pgerror); + ast_log(LOG_ERROR,"Connection may have been lost... attempting to reconnect.\n"); PQreset(conn); if (PQstatus(conn) == CONNECTION_OK) { - ast_log(LOG_ERROR, "cdr_pgsql: Connection reestablished.\n"); + ast_log(LOG_ERROR, "Connection reestablished.\n"); connected = 1; + pgunspool(); result = PQexec(conn, sqlcmd); if ( PQresultStatus(result) != PGRES_COMMAND_OK) { pgerror = PQresultErrorMessage(result); - ast_log(LOG_ERROR,"cdr_pgsql: HARD ERROR! Attempted reconnection failed. DROPPING CALL RECORD!\n"); - ast_log(LOG_ERROR,"cdr_pgsql: Reason: %s\n", pgerror); + if (pgspool) + ast_log(LOG_ERROR, "HARD ERROR! Reconnect Failed. Spooling Insert Commands to %s!!\n", pgspool); + else + ast_log(LOG_ERROR,"HARD ERROR! Attempted reconnection failed. DROPPING CALL RECORD!\n"); + ast_log(LOG_ERROR,"Reason: %s\n", pgerror); + pgspool_cmd(sqlcmd); } + } else { + pgspool_cmd(sqlcmd); } ast_mutex_unlock(&pgsql_lock); return -1; @@ -316,14 +428,44 @@ table = "cdr"; } - ast_log(LOG_DEBUG,"cdr_pgsql: got hostname of %s\n",pghostname); - ast_log(LOG_DEBUG,"cdr_pgsql: got port of %s\n",pgdbport); + /* Loading Spool File */ + tmp = ast_variable_retrieve(cfg,"global","spool"); + if (tmp) { + if (tmp[0] != '/') { + pgspool = malloc(strlen(tmp) + strlen(ast_config_AST_LOG_DIR) + 2); + if (pgspool != NULL) { + memset(pgspool, 0, strlen(tmp) + strlen(ast_config_AST_LOG_DIR) + 2); + pgspool_alloc = 1; + sprintf(pgspool, "%s/%s", ast_config_AST_LOG_DIR, tmp); + } else { + ast_log(LOG_ERROR, "Out of memory error.\n"); + return -1; + } + } else { + pgspool = malloc(strlen(tmp) + 1); + if (pgspool != NULL) { + memset(pgspool, 0, strlen(tmp)+1); + pgspool_alloc = 1; + strncpy(table, tmp, strlen(tmp)); + } else { + ast_log(LOG_ERROR, "Out of memory error.\n"); + return -1; + } + } + } else { + ast_log(LOG_WARNING, "Spool file not specified. If connection to database is lost, calls will be lost"); + } + + ast_log(LOG_DEBUG,"got hostname of %s\n",pghostname); + ast_log(LOG_DEBUG,"got port of %s\n",pgdbport); if (pgdbsock) - ast_log(LOG_DEBUG,"cdr_pgsql: got sock file of %s\n",pgdbsock); - ast_log(LOG_DEBUG,"cdr_pgsql: got user of %s\n",pgdbuser); - ast_log(LOG_DEBUG,"cdr_pgsql: got dbname of %s\n",pgdbname); - ast_log(LOG_DEBUG,"cdr_pgsql: got password of %s\n",pgpassword); - ast_log(LOG_DEBUG,"cdr_pgsql: got sql table name of %s\n",table); + ast_log(LOG_DEBUG,"sock file of %s\n",pgdbsock); + ast_log(LOG_DEBUG,"got user of %s\n",pgdbuser); + ast_log(LOG_DEBUG,"got dbname of %s\n",pgdbname); + ast_log(LOG_DEBUG,"got password of %s\n",pgpassword); + ast_log(LOG_DEBUG,"got sql table name of %s\n",table); + if (pgspool) + ast_log(LOG_DEBUG,"got spool file of %s\n",pgspool); conn = PQsetdbLogin(pghostname, pgdbport, NULL, NULL, pgdbname, pgdbuser, pgpassword); if (PQstatus(conn) != CONNECTION_BAD) { @@ -331,8 +473,11 @@ connected = 1; } else { pgerror = PQerrorMessage(conn); - ast_log(LOG_ERROR, "cdr_pgsql: Unable to connect to database server %s. CALLS WILL NOT BE LOGGED!!\n", pghostname); - ast_log(LOG_ERROR, "cdr_pgsql: Reason: %s\n", pgerror); + if (pgspool) + ast_log(LOG_ERROR, "Unable to connect to database server %s. Spooling Insert Commands to %s!!\n", pghostname, pgspool); + else + ast_log(LOG_ERROR, "Unable to connect to database server %s. CALLS WILL NOT BE LOGGED!!\n", pghostname); + ast_log(LOG_ERROR, "Reason: %s\n", pgerror); connected = 0; }