gnucash/libgnucash/engine/TransLog.cpp
2024-05-05 14:35:38 +08:00

304 lines
10 KiB
C++

/********************************************************************\
* 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 <config.h>
#ifdef __MINGW32__
#define __USE_MINGW_ANSI_STDIO 1
#endif
#include <errno.h>
#include <glib.h>
#include <glib/gstdio.h>
#include <string.h>
#include "Account.h"
#include "Transaction.h"
#include "TransactionP.hpp"
#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 = nullptr; /**< current log file handle */
static char * trans_log_name = nullptr; /**< current log file name */
static char * log_base_name = nullptr;
/********************************************************************\
\********************************************************************/
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", nullptr);
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 = nullptr;
}
/********************************************************************\
\********************************************************************/
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(nullptr), 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)
{
Split *split = GNC_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 *************************************/