GnuCash  5.6-150-g038405b370+
TransLog.cpp
1 /********************************************************************\
2  * TransLog.c -- the transaction logger *
3  * Copyright (C) 1998 Linas Vepstas *
4  * *
5  * This program is free software; you can redistribute it and/or *
6  * modify it under the terms of the GNU General Public License as *
7  * published by the Free Software Foundation; either version 2 of *
8  * the License, or (at your option) any later version. *
9  * *
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. *
14  * *
15  * You should have received a copy of the GNU General Public License*
16  * along with this program; if not, contact: *
17  * *
18  * Free Software Foundation Voice: +1-617-542-5942 *
19  * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
20  * Boston, MA 02110-1301, USA gnu@gnu.org *
21  * *
22 \********************************************************************/
23 
24 #include <config.h>
25 #ifdef __MINGW32__
26 #define __USE_MINGW_ANSI_STDIO 1
27 #endif
28 #include <errno.h>
29 #include <glib.h>
30 #include <glib/gstdio.h>
31 #include <string.h>
32 
33 #include "Account.h"
34 #include "Transaction.h"
35 #include "TransactionP.hpp"
36 #include "TransLog.h"
37 #include "qof.h"
38 #ifdef _MSC_VER
39 # define g_fopen fopen
40 #endif
41 
42 static QofLogModule log_module = "gnc.translog";
43 
44 /*
45  * Some design philosophy that I think would be good to keep in mind:
46  * (0) Simplicity and foolproofness are the over-riding design points.
47  * This is supposed to be a fail-safe safety net. We don't want
48  * our safety net to fail because of some whiz-bang shenanigans.
49  *
50  * (1) Try to keep the code simple. Want to make it simple and obvious
51  * that we are recording everything that we need to record.
52  *
53  * (2) Keep the printed format human readable, for the same reasons.
54  * (2.a) Keep the format, simple, flat, more or less unstructured,
55  * record oriented. This will help parsing by perl scripts.
56  * No, using a perl script to analyze a file that's supposed to
57  * be human readable is not a contradication in terms -- that's
58  * exactly the point.
59  * (2.b) Use tabs as a human friendly field separator; its also a
60  * character that does not (should not) appear naturally anywhere
61  * in the data, as it serves no formatting purpose in the current
62  * GUI design. (hack alert -- this is not currently tested for
63  * or enforced, so this is a very unsafe assumption. Maybe
64  * urlencoding should be used.)
65  * (2.c) Don't print redundant information in a single record. This
66  * would just confuse any potential user of this file.
67  * (2.d) Saving space, being compact is not a priority, I don't think.
68  *
69  * (3) There are no compatibility requirements from release to release.
70  * Sounds OK to me to change the format of the output when needed.
71  *
72  * (-) print transaction start and end delimiters
73  * (-) print a unique transaction id as a handy label for anyone
74  * who actually examines these logs.
75  * The C address pointer to the transaction struct should be fine,
76  * as it is simple and unique until the transaction is deleted ...
77  * and we log deletions, so that's OK. Just note that the id
78  * for a deleted transaction might be recycled.
79  * (-) print the current timestamp, so that if it is known that a bug
80  * occurred at a certain time, it can be located.
81  * (-) hack alert -- something better than just the account name
82  * is needed for identifying the account.
83  */
84 /* ------------------------------------------------------------------ */
85 
86 
87 static int gen_logs = 1;
88 static FILE * trans_log = nullptr;
89 static char * trans_log_name = nullptr;
90 static char * log_base_name = nullptr;
91 
92 /********************************************************************\
93 \********************************************************************/
94 
95 void xaccLogDisable (void)
96 {
97  gen_logs = 0;
98 }
99 void xaccLogEnable (void)
100 {
101  gen_logs = 1;
102 }
103 
104 /********************************************************************\
105 \********************************************************************/
106 
107 void
108 xaccReopenLog (void)
109 {
110  if (trans_log)
111  {
112  xaccCloseLog();
113  xaccOpenLog();
114  }
115 }
116 
117 
118 void
119 xaccLogSetBaseName (const char *basepath)
120 {
121  if (!basepath) return;
122 
123  g_free (log_base_name);
124  log_base_name = g_strdup (basepath);
125 
126  if (trans_log)
127  {
128  xaccCloseLog();
129  xaccOpenLog();
130  }
131 }
132 
133 
134 /*
135  * See if the provided file name is that of the current log file.
136  * Since the filename is generated with a time-stamp we can ignore the
137  * directory path and avoid problems with worrying about any ".."
138  * components in the path.
139  */
140 gboolean
141 xaccFileIsCurrentLog (const gchar *name)
142 {
143  gchar *base;
144  gint result;
145 
146  if (!name || !trans_log_name)
147  return FALSE;
148 
149  base = g_path_get_basename(name);
150  result = (strcmp(base, trans_log_name) == 0);
151  g_free(base);
152  return result;
153 }
154 
155 /********************************************************************\
156 \********************************************************************/
157 
158 void
159 xaccOpenLog (void)
160 {
161  char * filename;
162  char * timestamp;
163 
164  if (!gen_logs)
165  {
166  PINFO ("Attempt to open disabled transaction log");
167  return;
168  }
169  if (trans_log) return;
170 
171  if (!log_base_name) log_base_name = g_strdup ("translog");
172 
173  /* tag each filename with a timestamp */
174  timestamp = gnc_date_timestamp ();
175 
176  filename = g_strconcat (log_base_name, ".", timestamp, ".log", nullptr);
177 
178  trans_log = g_fopen (filename, "a");
179  if (!trans_log)
180  {
181  int norr = errno;
182  printf ("Error: xaccOpenLog(): cannot open journal\n"
183  "\t %d %s\n", norr, g_strerror (norr) ? g_strerror (norr) : "");
184 
185  g_free (filename);
186  g_free (timestamp);
187  return;
188  }
189 
190  /* Save the log file name */
191  if (trans_log_name)
192  g_free (trans_log_name);
193  trans_log_name = g_path_get_basename(filename);
194 
195  g_free (filename);
196  g_free (timestamp);
197 
198  /* Note: this must match src/import-export/log-replay/gnc-log-replay.c */
199  fprintf (trans_log, "mod\ttrans_guid\tsplit_guid\ttime_now\t"
200  "date_entered\tdate_posted\t"
201  "acc_guid\tacc_name\tnum\tdescription\t"
202  "notes\tmemo\taction\treconciled\t"
203  "amount\tvalue\tdate_reconciled\n");
204  fprintf (trans_log, "-----------------\n");
205 }
206 
207 /********************************************************************\
208 \********************************************************************/
209 
210 void
211 xaccCloseLog (void)
212 {
213  if (!trans_log) return;
214  fflush (trans_log);
215  fclose (trans_log);
216  trans_log = nullptr;
217 }
218 
219 /********************************************************************\
220 \********************************************************************/
221 
222 void
223 xaccTransWriteLog (Transaction *trans, char flag)
224 {
225  GList *node;
226  char trans_guid_str[GUID_ENCODING_LENGTH + 1];
227  char split_guid_str[GUID_ENCODING_LENGTH + 1];
228  const char *trans_notes;
229  char dnow[100], dent[100], dpost[100], drecn[100];
230 
231  if (!gen_logs)
232  {
233  PINFO ("Attempt to write disabled transaction log");
234  return;
235  }
236  if (!trans_log) return;
237 
238  gnc_time64_to_iso8601_buff (gnc_time(nullptr), dnow);
239  gnc_time64_to_iso8601_buff (trans->date_entered, dent);
240  gnc_time64_to_iso8601_buff (trans->date_posted, dpost);
241  guid_to_string_buff (xaccTransGetGUID(trans), trans_guid_str);
242  trans_notes = xaccTransGetNotes(trans);
243  fprintf (trans_log, "===== START\n");
244 
245  for (node = trans->splits; node; node = node->next)
246  {
247  Split *split = GNC_SPLIT(node->data);
248  const char * accname = "";
249  char acc_guid_str[GUID_ENCODING_LENGTH + 1];
250  gnc_numeric amt, val;
251 
252  if (xaccSplitGetAccount(split))
253  {
254  accname = xaccAccountGetName (xaccSplitGetAccount(split));
256  acc_guid_str);
257  }
258  else
259  {
260  acc_guid_str[0] = '\0';
261  }
262 
263  gnc_time64_to_iso8601_buff (split->date_reconciled, drecn);
264 
265  guid_to_string_buff (xaccSplitGetGUID(split), split_guid_str);
266  amt = xaccSplitGetAmount (split);
267  val = xaccSplitGetValue (split);
268 
269  /* use tab-separated fields */
270  fprintf (trans_log,
271  "%c\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t"
272  "%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",
273  flag,
274  trans_guid_str, split_guid_str, /* trans+split make up unique id */
275  /* Note that the next three strings always exist,
276  * so we don't need to test them. */
277  dnow,
278  dent,
279  dpost,
280  acc_guid_str,
281  accname ? accname : "",
282  trans->num ? trans->num : "",
283  trans->description ? trans->description : "",
284  trans_notes ? trans_notes : "",
285  split->memo ? split->memo : "",
286  split->action ? split->action : "",
287  split->reconciled,
288  gnc_numeric_num(amt),
289  gnc_numeric_denom(amt),
290  gnc_numeric_num(val),
291  gnc_numeric_denom(val),
292  /* The next string always exists. No need to test it. */
293  drecn);
294  }
295 
296  fprintf (trans_log, "===== END\n");
297 
298  /* get data out to the disk */
299  fflush (trans_log);
300 }
301 
302 /************************ END OF ************************************\
303 \************************* FILE *************************************/
char * gnc_date_timestamp(void)
Make a timestamp in YYYYMMDDHHMMSS format.
Definition: gnc-date.cpp:1100
#define PINFO(format, args...)
Print an informational note.
Definition: qoflog.h:256
void xaccTransWriteLog(Transaction *trans, char flag)
Definition: TransLog.cpp:223
void xaccLogDisable(void)
document me
Definition: TransLog.cpp:95
gchar * guid_to_string_buff(const GncGUID *guid, gchar *str)
The guid_to_string_buff() routine puts a null-terminated string encoding of the id into the memory po...
Definition: guid.cpp:173
const char * xaccTransGetNotes(const Transaction *trans)
Gets the transaction Notes.
#define xaccAccountGetGUID(X)
Definition: Account.h:248
Account handling public routines.
#define GUID_ENCODING_LENGTH
Number of characters needed to encode a guid as a string not including the null terminator.
Definition: guid.h:84
#define xaccSplitGetGUID(X)
Definition: Split.h:552
#define xaccTransGetGUID(X)
Definition: Transaction.h:788
API for the transaction logger.
void xaccLogSetBaseName(const char *basepath)
The xaccLogSetBaseName() method sets the base filepath and the root part of the journal file name...
Definition: TransLog.cpp:119
gnc_numeric xaccSplitGetValue(const Split *split)
Returns the value of this split in the transaction&#39;s commodity.
Definition: gmock-Split.cpp:84
Account * xaccSplitGetAccount(const Split *split)
Returns the account of this split, which was set through xaccAccountInsertSplit().
Definition: gmock-Split.cpp:53
gboolean xaccFileIsCurrentLog(const gchar *name)
Test a filename to see if it is the name of the current logfile.
Definition: TransLog.cpp:141
time64 gnc_time(time64 *tbuf)
get the current time
Definition: gnc-date.cpp:261
const char * xaccAccountGetName(const Account *acc)
Get the account&#39;s name.
Definition: Account.cpp:3250
API for Transactions and Splits (journal entries)
char * gnc_time64_to_iso8601_buff(time64 time, char *buff)
The gnc_time64_to_iso8601_buff() routine takes the input UTC time64 value and prints it as an ISO-860...
Definition: gnc-date.cpp:1139
void xaccLogEnable(void)
document me
Definition: TransLog.cpp:99
gnc_numeric xaccSplitGetAmount(const Split *split)
Returns the amount of the split in the account&#39;s commodity.
Definition: gmock-Split.cpp:69