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