qLibc
qlog.c
Go to the documentation of this file.
1 /******************************************************************************
2  * qLibc
3  *
4  * Copyright (c) 2010-2015 Seungyoung Kim.
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions are met:
9  *
10  * 1. Redistributions of source code must retain the above copyright notice,
11  * this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright notice,
13  * this list of conditions and the following disclaimer in the documentation
14  * and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
20  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26  * POSSIBILITY OF SUCH DAMAGE.
27  *****************************************************************************/
28 
29 /**
30  * @file qlog.c Rotating file logger object.
31  *
32  * qlog implements a auto-rotating file logger.
33  *
34  * @code
35  * // create a daily-rotating log file.
36  * qlog_t *log = qlog("/tmp/dailylog-%Y%m%d.err", 0644, 86400, false);
37  * if(log == NULL) return;
38  *
39  * // screen out.
40  * log->duplicate(log, stdout, true);
41  *
42  * // write logs.
43  * log->write(log, "Service started.");
44  * log->writef(log, "Server Id: %d", 1);
45  *
46  * // close and release resources.
47  * log->free(log);
48  * @endcode
49  */
50 
51 #ifndef DISABLE_QLOG
52 
53 #include <stdio.h>
54 #include <stdlib.h>
55 #include <stdbool.h>
56 #include <string.h>
57 #include <stdarg.h>
58 #include <unistd.h>
59 #include <time.h>
60 #include <sys/stat.h>
61 #include <errno.h>
62 #include "qinternal.h"
63 #include "utilities/qstring.h"
64 #include "extensions/qlog.h"
65 
66 #ifndef _DOXYGEN_SKIP
67 static bool write_(qlog_t *log, const char *str);
68 static bool writef(qlog_t *log, const char *format, ...);
69 static bool duplicate(qlog_t *log, FILE *outfp, bool flush);
70 static bool flush_(qlog_t *log);
71 static void free_(qlog_t *log);
72 
73 // internal usages
74 static bool _real_open(qlog_t *log);
75 #endif
76 
77 /**
78  * Open ratating-log file
79  *
80  * @param filepathfmt filename format. formatting argument is same as
81  * strftime()
82  * @param mode new file mode. 0 for system default
83  * @param rotateinterval rotating interval seconds, set 0 to disable rotation
84  * @param options combination of options.
85  *
86  * @return a pointer of qlog_t structure
87  *
88  * @note
89  * rotateinterval is not relative time. If you set it to 3600, log file will be
90  * rotated at every hour. And filenameformat is same as strftime(). So If you
91  * want to log with hourly rotating, filenameformat must be defined including
92  * hour format like "/somepath/xxx-%Y%m%d%H.log". You can set it to
93  * "/somepath/xxx-%H.log" for daily overrided log file.
94  *
95  * @note
96  * Available options:
97  * - QLOG_OPT_THREADSAFE - make it thread-safe.
98  * - QLOG_OPT_FLUSH - flush out buffer everytime.
99  *
100  * @code
101  * qlog_t *log = qlog("/tmp/qdecoder-%Y%m%d.err", 0644, 86400, QLOG_OPT_THREADSAFE);
102  * log->free(log);
103  * @endcode
104  */
105 qlog_t *qlog(const char *filepathfmt, mode_t mode, int rotateinterval,
106  int options) {
107  qlog_t *log;
108 
109  // malloc qlog_t structure
110  log = (qlog_t *) calloc(1, sizeof(qlog_t));
111  if (log == NULL) {
112  errno = ENOMEM;
113  return NULL;
114  }
115 
116  // set up the structure.
117  qstrcpy(log->filepathfmt, sizeof(log->filepathfmt), filepathfmt);
118  log->mode = mode;
119  if (rotateinterval > 0)
120  log->rotateinterval = rotateinterval;
121 
122  // handle options
123  if (options & QLOG_OPT_THREADSAFE) {
124  Q_MUTEX_NEW(log->qmutex, true);
125  if (log->qmutex == NULL) {
126  errno = ENOMEM;
127  free(log);
128  return NULL;
129  }
130  }
131  if (options & QLOG_OPT_FLUSH) {
132  log->logflush = true;
133  }
134 
135  // try to open the log file.
136  if (_real_open(log) == false) {
137  free(log);
138  Q_MUTEX_DESTROY(log->qmutex);
139  return NULL;
140  }
141 
142  // member methods
143  log->write = write_;
144  log->writef = writef;
145  log->duplicate = duplicate;
146  log->flush = flush_;
147  log->free = free_;
148 
149  return log;
150 }
151 
152 /**
153  * qlog->write(): Log messages
154  *
155  * @param log a pointer of qlog_t
156  * @param str message string
157  *
158  * @return true if successful, otherewise returns false
159  */
160 static bool write_(qlog_t *log, const char *str) {
161  if (log == NULL || log->fp == NULL)
162  return false;
163 
164  Q_MUTEX_ENTER(log->qmutex);
165 
166  /* duplicate stream */
167  if (log->outfp != NULL) {
168  fprintf(log->outfp, "%s\n", str);
169  if (log->outflush == true)
170  fflush(log->outfp);
171  }
172 
173  /* check if log rotation is needed */
174  if (log->nextrotate > 0 && time(NULL) >= log->nextrotate) {
175  _real_open(log);
176  }
177 
178  /* log to file */
179  bool ret = false;
180  if (fprintf(log->fp, "%s\n", str) >= 0) {
181  if (log->logflush == true)
182  fflush(log->fp);
183  ret = true;
184  }
185 
186  Q_MUTEX_LEAVE(log->qmutex);
187 
188  return ret;
189 }
190 
191 /**
192  * qlog->writef(): Log messages
193  *
194  * @param log a pointer of qlog_t
195  * @param format messages format
196  *
197  * @return true if successful, otherewise returns false
198  */
199 static bool writef(qlog_t *log, const char *format, ...) {
200  if (log == NULL || log->fp == NULL)
201  return false;
202 
203  char *str;
204  DYNAMIC_VSPRINTF(str, format);
205  if (str == NULL)
206  return false;
207 
208  bool ret = write_(log, str);
209 
210  free(str);
211  return ret;
212 }
213 
214 /**
215  * qlog->duplicate(): Duplicate log string into other stream
216  *
217  * @param log a pointer of qlog_t
218  * @param fp logging messages will be printed out into this stream.
219  * set NULL to disable.
220  * @param flush set to true if you want to flush everytime duplicating.
221  *
222  * @return true if successful, otherewise returns false
223  *
224  * @code
225  * log->duplicate(log, stdout, true); // enable console out with flushing
226  * log->duplicate(log, stderr, false); // enable console out
227  * log->duplicate(log, NULL, false); // disable console out (default)
228  * @endcode
229  */
230 static bool duplicate(qlog_t *log, FILE *outfp, bool flush) {
231  if (log == NULL)
232  return false;
233 
234  Q_MUTEX_ENTER(log->qmutex);
235  log->outfp = outfp;
236  log->outflush = flush;
237  Q_MUTEX_LEAVE(log->qmutex);
238 
239  return true;
240 }
241 
242 /**
243  * qlog->flush(): Flush buffered log
244  *
245  * @param log a pointer of qlog_t
246  *
247  * @return true if successful, otherewise returns false
248  */
249 static bool flush_(qlog_t *log) {
250  if (log == NULL)
251  return false;
252 
253  // only flush if flush flag is disabled
254  Q_MUTEX_ENTER(log->qmutex);
255  if (log->fp != NULL && log->logflush == false)
256  fflush(log->fp);
257  if (log->outfp != NULL && log->outflush == false)
258  fflush(log->outfp);
259  Q_MUTEX_LEAVE(log->qmutex);
260 
261  return false;
262 }
263 
264 /**
265  * qlog->free(): Close ratating-log file & de-allocate resources
266  *
267  * @param log a pointer of qlog_t
268  */
269 static void free_(qlog_t *log) {
270  if (log == NULL)
271  return;
272 
273  flush_(log);
274  Q_MUTEX_ENTER(log->qmutex);
275  if (log->fp != NULL) {
276  fclose(log->fp);
277  log->fp = NULL;
278  }
279  Q_MUTEX_LEAVE(log->qmutex);
280  Q_MUTEX_DESTROY(log->qmutex);
281  free(log);
282  return;
283 }
284 
285 #ifndef _DOXYGEN_SKIP
286 
287 static bool _real_open(qlog_t *log) {
288  const time_t nowtime = time(NULL);
289 
290  /* generate filename */
291  char newfilepath[PATH_MAX];
292  strftime(newfilepath, sizeof(newfilepath), log->filepathfmt,
293  localtime(&nowtime));
294 
295  /* open or re-open log file */
296  if (log->fp == NULL) {
297  log->fp = fopen(newfilepath, "a");
298  if (log->fp == NULL) {
299  DEBUG("_real_open: Can't open log file '%s'.", newfilepath);
300  return false;
301  }
302 
303  if (log->mode != 0)
304  fchmod(fileno(log->fp), log->mode);
305  qstrcpy(log->filepath, sizeof(log->filepath), newfilepath);
306  } else if (strcmp(log->filepath, newfilepath)) {
307  /* have opened stream, only reopen if new filename is different with
308  existing one */
309  FILE *newfp = fopen(newfilepath, "a");
310  if (newfp != NULL) {
311  if (log->mode != 0)
312  fchmod(fileno(newfp), log->mode);
313  fclose(log->fp);
314  log->fp = newfp;
315  qstrcpy(log->filepath, sizeof(log->filepath), newfilepath);
316  } else {
317  DEBUG("_real_open: Can't open log file '%s' for rotating.",
318  newfilepath);
319  }
320  } else {
321  DEBUG("_real_open: skip re-opening log file.");
322  }
323 
324  /* set next rotate time */
325  if (log->rotateinterval > 0) {
326  time_t ct = time(NULL);
327  time_t dt = ct - mktime(gmtime(&ct));
328  log->nextrotate = (((ct + dt) / log->rotateinterval) + 1)
329  * log->rotateinterval - dt;
330  } else {
331  log->nextrotate = 0;
332  }
333 
334  return true;
335 }
336 
337 #endif
338 
339 #endif /* DISABLE_QLOG */
static bool write_(qlog_t *log, const char *str)
qlog->write(): Log messages
Definition: qlog.c:160
static bool flush_(qlog_t *log)
qlog->flush(): Flush buffered log
Definition: qlog.c:249
qlog_t * qlog(const char *filepathfmt, mode_t mode, int rotateinterval, int options)
Open ratating-log file.
Definition: qlog.c:105
static bool duplicate(qlog_t *log, FILE *outfp, bool flush)
qlog->duplicate(): Duplicate log string into other stream
Definition: qlog.c:230
char * qstrcpy(char *dst, size_t size, const char *src)
Copy src string to dst.
Definition: qstring.c:320
static void free_(qlog_t *log)
qlog->free(): Close ratating-log file & de-allocate resources
Definition: qlog.c:269
static bool writef(qlog_t *log, const char *format,...)
qlog->writef(): Log messages
Definition: qlog.c:199