/********************************************************************\ * TransLog.c -- the transaction logger * * Copyright (C) 1998 Linas Vepstas * * * * 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, contact: * * * * Free Software Foundation Voice: +1-617-542-5942 * * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 * * Boston, MA 02110-1301, USA gnu@gnu.org * * * \********************************************************************/ #include #ifdef __MINGW32__ #define __USE_MINGW_ANSI_STDIO 1 #endif #include #include #include #include #include "Account.h" #include "Transaction.h" #include "TransactionP.h" #include "TransLog.h" #include "qof.h" #ifdef _MSC_VER # define g_fopen fopen #endif static QofLogModule log_module = "gnc.translog"; /* * Some design philosophy that I think would be good to keep in mind: * (0) Simplicity and foolproofness are the over-riding design points. * This is supposed to be a fail-safe safety net. We don't want * our safety net to fail because of some whiz-bang shenanigans. * * (1) Try to keep the code simple. Want to make it simple and obvious * that we are recording everything that we need to record. * * (2) Keep the printed format human readable, for the same reasons. * (2.a) Keep the format, simple, flat, more or less unstructured, * record oriented. This will help parsing by perl scripts. * No, using a perl script to analyze a file that's supposed to * be human readable is not a contradication in terms -- that's * exactly the point. * (2.b) Use tabs as a human friendly field separator; its also a * character that does not (should not) appear naturally anywhere * in the data, as it serves no formatting purpose in the current * GUI design. (hack alert -- this is not currently tested for * or enforced, so this is a very unsafe assumption. Maybe * urlencoding should be used.) * (2.c) Don't print redundant information in a single record. This * would just confuse any potential user of this file. * (2.d) Saving space, being compact is not a priority, I don't think. * * (3) There are no compatibility requirements from release to release. * Sounds OK to me to change the format of the output when needed. * * (-) print transaction start and end delimiters * (-) print a unique transaction id as a handy label for anyone * who actually examines these logs. * The C address pointer to the transaction struct should be fine, * as it is simple and unique until the transaction is deleted ... * and we log deletions, so that's OK. Just note that the id * for a deleted transaction might be recycled. * (-) print the current timestamp, so that if it is known that a bug * occurred at a certain time, it can be located. * (-) hack alert -- something better than just the account name * is needed for identifying the account. */ /* ------------------------------------------------------------------ */ static int gen_logs = 1; static FILE * trans_log = NULL; /**< current log file handle */ static char * trans_log_name = NULL; /**< current log file name */ static char * log_base_name = NULL; /********************************************************************\ \********************************************************************/ void xaccLogDisable (void) { gen_logs = 0; } void xaccLogEnable (void) { gen_logs = 1; } /********************************************************************\ \********************************************************************/ void xaccReopenLog (void) { if (trans_log) { xaccCloseLog(); xaccOpenLog(); } } void xaccLogSetBaseName (const char *basepath) { if (!basepath) return; g_free (log_base_name); log_base_name = g_strdup (basepath); if (trans_log) { xaccCloseLog(); xaccOpenLog(); } } /* * See if the provided file name is that of the current log file. * Since the filename is generated with a time-stamp we can ignore the * directory path and avoid problems with worrying about any ".." * components in the path. */ gboolean xaccFileIsCurrentLog (const gchar *name) { gchar *base; gint result; if (!name || !trans_log_name) return FALSE; base = g_path_get_basename(name); result = (strcmp(base, trans_log_name) == 0); g_free(base); return result; } /********************************************************************\ \********************************************************************/ void xaccOpenLog (void) { char * filename; char * timestamp; if (!gen_logs) { PINFO ("Attempt to open disabled transaction log"); return; } if (trans_log) return; if (!log_base_name) log_base_name = g_strdup ("translog"); /* tag each filename with a timestamp */ timestamp = gnc_date_timestamp (); filename = g_strconcat (log_base_name, ".", timestamp, ".log", NULL); trans_log = g_fopen (filename, "a"); if (!trans_log) { int norr = errno; printf ("Error: xaccOpenLog(): cannot open journal\n" "\t %d %s\n", norr, g_strerror (norr) ? g_strerror (norr) : ""); g_free (filename); g_free (timestamp); return; } /* Save the log file name */ if (trans_log_name) g_free (trans_log_name); trans_log_name = g_path_get_basename(filename); g_free (filename); g_free (timestamp); /* Note: this must match src/import-export/log-replay/gnc-log-replay.c */ fprintf (trans_log, "mod\ttrans_guid\tsplit_guid\ttime_now\t" "date_entered\tdate_posted\t" "acc_guid\tacc_name\tnum\tdescription\t" "notes\tmemo\taction\treconciled\t" "amount\tvalue\tdate_reconciled\n"); fprintf (trans_log, "-----------------\n"); } /********************************************************************\ \********************************************************************/ void xaccCloseLog (void) { if (!trans_log) return; fflush (trans_log); fclose (trans_log); trans_log = NULL; } /********************************************************************\ \********************************************************************/ void xaccTransWriteLog (Transaction *trans, char flag) { GList *node; char trans_guid_str[GUID_ENCODING_LENGTH + 1]; char split_guid_str[GUID_ENCODING_LENGTH + 1]; const char *trans_notes; char dnow[100], dent[100], dpost[100], drecn[100]; if (!gen_logs) { PINFO ("Attempt to write disabled transaction log"); return; } if (!trans_log) return; gnc_time64_to_iso8601_buff (gnc_time(NULL), dnow); gnc_time64_to_iso8601_buff (trans->date_entered, dent); gnc_time64_to_iso8601_buff (trans->date_posted, dpost); guid_to_string_buff (xaccTransGetGUID(trans), trans_guid_str); trans_notes = xaccTransGetNotes(trans); fprintf (trans_log, "===== START\n"); for (node = trans->splits; node; node = node->next) { time64 time; Split *split = node->data; const char * accname = ""; char acc_guid_str[GUID_ENCODING_LENGTH + 1]; gnc_numeric amt, val; if (xaccSplitGetAccount(split)) { accname = xaccAccountGetName (xaccSplitGetAccount(split)); guid_to_string_buff(xaccAccountGetGUID(xaccSplitGetAccount(split)), acc_guid_str); } else { acc_guid_str[0] = '\0'; } gnc_time64_to_iso8601_buff (split->date_reconciled, drecn); guid_to_string_buff (xaccSplitGetGUID(split), split_guid_str); amt = xaccSplitGetAmount (split); val = xaccSplitGetValue (split); /* use tab-separated fields */ fprintf (trans_log, "%c\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t" "%s\t%s\t%s\t%s\t%c\t%" G_GINT64_FORMAT "/%" G_GINT64_FORMAT "\t%" G_GINT64_FORMAT "/%" G_GINT64_FORMAT "\t%s\n", flag, trans_guid_str, split_guid_str, /* trans+split make up unique id */ /* Note that the next three strings always exist, * so we don't need to test them. */ dnow, dent, dpost, acc_guid_str, accname ? accname : "", trans->num ? trans->num : "", trans->description ? trans->description : "", trans_notes ? trans_notes : "", split->memo ? split->memo : "", split->action ? split->action : "", split->reconciled, gnc_numeric_num(amt), gnc_numeric_denom(amt), gnc_numeric_num(val), gnc_numeric_denom(val), /* The next string always exists. No need to test it. */ drecn); } fprintf (trans_log, "===== END\n"); /* get data out to the disk */ fflush (trans_log); } /************************ END OF ************************************\ \************************* FILE *************************************/