1
/*********************************************************
2
* Copyright (C) 2010 VMware, Inc. All rights reserved.
4
* This program is free software; you can redistribute it and/or modify it
5
* under the terms of the GNU Lesser General Public License as published
6
* by the Free Software Foundation version 2.1 and no later version.
8
* This program is distributed in the hope that it will be useful, but
9
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10
* or FITNESS FOR A PARTICULAR PURPOSE. See the Lesser GNU General Public
11
* License for more details.
13
* You should have received a copy of the GNU Lesser General Public License
14
* along with this program; if not, write to the Free Software Foundation, Inc.,
15
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
17
*********************************************************/
22
* Logger that uses file streams and provides optional log rotation.
25
#include "glibUtils.h"
28
#include <glib/gstdio.h>
29
#if defined(G_PLATFORM_WIN32)
38
typedef struct FileLogger {
53
*******************************************************************************
54
* FileLoggerIsValid -- */ /**
56
* Checks that the file descriptor backing this logger is still valid.
58
* This is a racy workaround for an issue with glib code; or, rather, two
59
* issues. The first issue is that we can't intercept G_LOG_FLAG_RECURSION,
60
* and glib just aborts when that happens (see gnome bug 618956). The second
61
* is that if a GIOChannel channel write fails, that calls
62
* g_io_channel_error_from_errno, which helpfully logs stuff, causing recursion.
63
* Don't get me started on why that's, well, at least questionable.
65
* This is racy because between the check and the actual GIOChannel operation,
66
* the state of the FD may have changed. In reality, since the bug this is
67
* fixing happens in very special situations where code outside this file is
68
* doing weird things like closing random fds, it should be OK.
70
* We may still get other write errors from the GIOChannel than EBADF, but
71
* those would be harder to work around. Hopefully this handles the most usual
74
* See bug 783999 for some details about what triggers the bug.
76
* @param[in] logger The logger instance.
78
* @return TRUE if the I/O channel is still valid.
80
*******************************************************************************
84
FileLoggerIsValid(FileLogger *logger)
86
if (logger->file != NULL) {
87
int fd = g_io_channel_unix_get_fd(logger->file);
88
return fcntl(fd, F_GETFD) >= 0;
96
#define FileLoggerIsValid(logger) TRUE
102
*******************************************************************************
103
* FileLoggerGetPath -- */ /**
105
* Parses the given template file name and expands embedded variables, and
106
* places the log index information at the right position.
108
* The following variables are expanded:
110
* - ${USER}: user's login name.
111
* - ${PID}: current process's pid.
112
* - ${IDX}: index of the log file (for rotation).
114
* @param[in] data Log handler data.
115
* @param[in] index Index of the log file.
117
* @return The expanded log file path.
119
******************************************************************************
123
FileLoggerGetPath(FileLogger *data,
126
gboolean hasIndex = FALSE;
137
logpath = g_strdup(data->path);
138
vars[1] = (char *) g_get_user_name();
139
vars[3] = g_strdup_printf("%u", (unsigned int) getpid());
140
g_snprintf(indexStr, sizeof indexStr, "%d", index);
142
for (i = 0; i < G_N_ELEMENTS(vars); i += 2) {
143
char *last = logpath;
145
while ((start = strstr(last, vars[i])) != NULL) {
147
char *end = start + strlen(vars[i]);
148
size_t offset = (start - last) + strlen(vars[i+1]);
151
tmp = g_strdup_printf("%s%s%s", logpath, vars[i+1], end);
154
last = logpath + offset;
156
/* XXX: ugly, but well... */
166
* Always make sure we add the index if it's not 0, since that's used for
167
* backing up old log files.
169
if (index != 0 && !hasIndex) {
170
char *sep = strrchr(logpath, '.');
171
char *pathsep = strrchr(logpath, '/');
173
if (pathsep == NULL) {
174
pathsep = strrchr(logpath, '\\');
177
if (sep != NULL && sep > pathsep) {
180
tmp = g_strdup_printf("%s.%d.%s", logpath, index, sep);
182
tmp = g_strdup_printf("%s.%d", logpath, index);
193
*******************************************************************************
194
* FileLoggerOpen -- */ /**
196
* Opens a log file for writing, backing up the existing log file if one is
197
* present. Only one old log file is preserved.
199
* @note Make sure this function is called with the write lock held.
201
* @param[in] data Log handler data.
203
* @return Log file pointer (NULL on error).
205
*******************************************************************************
209
FileLoggerOpen(FileLogger *data)
211
GIOChannel *logfile = NULL;
214
g_return_val_if_fail(data != NULL, NULL);
215
path = FileLoggerGetPath(data, 0);
217
if (g_file_test(path, G_FILE_TEST_EXISTS)) {
219
if (g_stat(path, &fstats) > -1) {
220
data->logSize = (gint) fstats.st_size;
223
if (!data->append || data->logSize >= data->maxSize) {
225
* Find the last log file and iterate back, changing the indices as we go,
226
* so that the oldest log file has the highest index (the new log file
227
* will always be index "0"). When not rotating, "maxFiles" is 1, so we
228
* always keep one backup.
232
GPtrArray *logfiles = g_ptr_array_new();
235
* Find the id of the last log file. The pointer array will hold
236
* the names of all existing log files + the name of the last log
237
* file, which may or may not exist.
239
for (id = 0; id < data->maxFiles; id++) {
240
log = FileLoggerGetPath(data, id);
241
g_ptr_array_add(logfiles, log);
242
if (!g_file_test(log, G_FILE_TEST_IS_REGULAR)) {
247
/* Rename the existing log files, increasing their index by 1. */
248
for (id = logfiles->len - 1; id > 0; id--) {
249
gchar *dest = g_ptr_array_index(logfiles, id);
250
gchar *src = g_ptr_array_index(logfiles, id - 1);
252
if (!g_file_test(dest, G_FILE_TEST_IS_DIR) &&
253
(!g_file_test(dest, G_FILE_TEST_EXISTS) ||
254
g_unlink(dest) == 0)) {
262
for (id = 0; id < logfiles->len; id++) {
263
g_free(g_ptr_array_index(logfiles, id));
265
g_ptr_array_free(logfiles, TRUE);
267
data->append = FALSE;
271
logfile = g_io_channel_new_file(path, data->append ? "a" : "w", NULL);
274
if (logfile != NULL) {
275
g_io_channel_set_encoding(logfile, NULL, NULL);
283
*******************************************************************************
284
* FileLoggerLog -- */ /**
286
* Logs a message to the configured destination file. Also opens the file for
287
* writing if it hasn't been done yet.
289
* @param[in] domain Log domain.
290
* @param[in] level Log level.
291
* @param[in] message Message to log.
292
* @param[in] data File logger.
294
*******************************************************************************
298
FileLoggerLog(const gchar *domain,
299
GLogLevelFlags level,
300
const gchar *message,
303
FileLogger *logger = data;
306
g_static_mutex_lock(&logger->lock);
312
if (logger->file == NULL) {
313
if (logger->file == NULL) {
314
logger->file = FileLoggerOpen(data);
316
if (logger->file == NULL) {
317
logger->error = TRUE;
322
if (!FileLoggerIsValid(logger)) {
323
logger->error = TRUE;
327
/* Write the log file and do log rotation accounting. */
328
if (g_io_channel_write_chars(logger->file, message, -1, &written, NULL) ==
329
G_IO_STATUS_NORMAL) {
330
if (logger->maxSize > 0) {
331
logger->logSize += (gint) written;
332
if (logger->logSize >= logger->maxSize) {
333
g_io_channel_unref(logger->file);
334
logger->append = FALSE;
335
logger->file = FileLoggerOpen(logger);
337
g_io_channel_flush(logger->file, NULL);
340
g_io_channel_flush(logger->file, NULL);
345
g_static_mutex_unlock(&logger->lock);
350
******************************************************************************
351
* FileLoggerDestroy -- */ /**
353
* Cleans up the internal state of a file logger.
355
* @param[in] _data File logger data.
357
******************************************************************************
361
FileLoggerDestroy(gpointer data)
363
FileLogger *logger = data;
364
if (logger->file != NULL) {
365
g_io_channel_unref(logger->file);
367
g_static_mutex_free(&logger->lock);
368
g_free(logger->path);
374
*******************************************************************************
375
* GlibUtils_CreateFileLogger -- */ /**
377
* @brief Creates a new file logger based on the given configuration.
379
* @param[in] path Path to log file.
380
* @param[in] append Whether to append to existing log file.
381
* @param[in] maxSize Maximum log file size (in MB, 0 = no limit).
382
* @param[in] maxFiles Maximum number of old files to be kept.
384
* @return A new logger, or NULL on error.
386
*******************************************************************************
390
GlibUtils_CreateFileLogger(const char *path,
395
FileLogger *data = NULL;
397
g_return_val_if_fail(path != NULL, NULL);
399
data = g_new0(FileLogger, 1);
400
data->handler.addsTimestamp = FALSE;
401
data->handler.shared = FALSE;
402
data->handler.logfn = FileLoggerLog;
403
data->handler.dtor = FileLoggerDestroy;
405
data->path = g_filename_from_utf8(path, -1, NULL, NULL, NULL);
406
if (data->path == NULL) {
411
data->append = append;
412
data->maxSize = maxSize * 1024 * 1024;
413
data->maxFiles = maxFiles + 1; /* To account for the active log file. */
414
g_static_mutex_init(&data->lock);
416
return &data->handler;