]> mj.ucw.cz Git - libucw.git/blob - ucw/log-file.c
eebdb1aa0f6d603277e0d19cadd7058456260e50
[libucw.git] / ucw / log-file.c
1 /*
2  *      UCW Library -- Logging to Files
3  *
4  *      (c) 1997--2009 Martin Mares <mj@ucw.cz>
5  *      (c) 2008 Tomas Gavenciak <gavento@ucw.cz>
6  *
7  *      This software may be freely distributed and used according to the terms
8  *      of the GNU Lesser General Public License.
9  */
10
11 #include "ucw/lib.h"
12 #include "ucw/log.h"
13 #include "ucw/lfs.h"
14 #include "ucw/threads.h"
15 #include "ucw/simple-lists.h"
16
17 #include <stdio.h>
18 #include <string.h>
19 #include <fcntl.h>
20 #include <unistd.h>
21 #include <time.h>
22
23 struct file_stream {
24   struct log_stream ls;         // ls.name is the current name of the log file
25   int fd;
26   uns flags;                    // FF_xxx
27   char *orig_name;              // Original name with strftime escapes
28 };
29
30 enum log_file_flag {
31   FF_FORMAT_NAME = 1,           // Name contains strftime escapes
32   FF_CLOSE_FD = 2,              // Close the fd with the stream
33   FF_FD2_FOLLOWS = 4,           // Maintain stderr as a clone of this stream
34 };
35
36 #define MAX_EXPAND 64           // Maximum size of expansion of strftime escapes
37
38 static int log_switch_nest;
39
40 static void
41 do_log_reopen(struct file_stream *fs, const char *name)
42 {
43   int fd = ucw_open(name, O_WRONLY | O_CREAT | O_APPEND, 0666);
44   if (fd < 0)
45     die("Unable to open log file %s: %m", name);
46   if (fs->fd >= 0)
47     close(fs->fd);
48   fs->fd = fd;
49   if (fs->flags & FF_FD2_FOLLOWS)
50     dup2(fd, 2);
51   if (fs->ls.name)
52     {
53       xfree(fs->ls.name);
54       fs->ls.name = NULL;       // We have to keep the stream consistent -- die() below can invoke logging
55     }
56   fs->ls.name = xstrdup(name);
57 }
58
59 static int
60 do_log_switch(struct file_stream *fs, struct tm *tm)
61 {
62   if (!(fs->flags & FF_FORMAT_NAME))
63     {
64       if (fs->fd >= 0)
65         return 1;
66       else
67         {
68           do_log_reopen(fs, fs->orig_name);
69           return 1;
70         }
71     }
72
73   int buflen = strlen(fs->orig_name) + MAX_EXPAND;
74   char name[buflen];
75   int switched = 0;
76
77   ucwlib_lock();
78   if (!log_switch_nest)         // Avoid infinite loops if we die when switching logs
79     {
80       log_switch_nest++;
81       int l = strftime(name, buflen, fs->orig_name, tm);
82       if (l < 0 || l >= buflen)
83         die("Error formatting log file name: %m");
84       if (!fs->ls.name || strcmp(name, fs->ls.name))
85         {
86           do_log_reopen(fs, name);
87           switched = 1;
88         }
89       log_switch_nest--;
90     }
91   ucwlib_unlock();
92   return switched;
93 }
94
95 /* destructor for standard files */
96 static void
97 file_close(struct log_stream *ls)
98 {
99   struct file_stream *fs = (struct file_stream *) ls;
100   if ((fs->flags & FF_CLOSE_FD) && fs->fd >= 0)
101     close(fs->fd);
102   xfree(fs->ls.name);
103   xfree(fs->orig_name);
104 }
105
106 /* handler for standard files */
107 static int
108 file_handler(struct log_stream *ls, struct log_msg *m)
109 {
110   struct file_stream *fs = (struct file_stream *) ls;
111   if ((fs->flags & FF_FORMAT_NAME) && m->tm)
112     do_log_switch(fs, m->tm);
113
114   int r = write(fs->fd, m->m, m->m_len);
115   /* FIXME: check for errors here? */
116   return 0;
117 }
118
119 /* assign log to a file descriptor */
120 /* initialize with the default formatting, does NOT close the descriptor */
121 struct log_stream *
122 log_new_fd(int fd)
123 {
124   struct log_stream *ls = log_new_stream(sizeof(struct file_stream));
125   struct file_stream *fs = (struct file_stream *) ls;
126   fs->fd = fd;
127   ls->msgfmt = LSFMT_DEFAULT;
128   ls->handler = file_handler;
129   ls->close = file_close;
130   ls->name = xmalloc(16);
131   snprintf(ls->name, 16, "fd%d", fd);
132   return ls;
133 }
134
135 static struct log_stream *
136 do_log_new_file(const char *path, uns more_flags)
137 {
138   struct log_stream *ls = log_new_stream(sizeof(struct file_stream));
139   struct file_stream *fs = (struct file_stream *) ls;
140   fs->fd = -1;
141   fs->orig_name = xstrdup(path);
142   if (strchr(path, '%'))
143     fs->flags = FF_FORMAT_NAME;
144   fs->flags |= FF_CLOSE_FD | more_flags;
145   ls->msgfmt = LSFMT_DEFAULT;
146   ls->handler = file_handler;
147   ls->close = file_close;
148
149   time_t now = time(NULL);
150   struct tm *tm = localtime(&now);
151   ASSERT(tm);
152   do_log_switch(fs, tm);                // die()'s on errors
153   return ls;
154 }
155
156 /* open() a file (append mode) */
157 /* initialize with the default formatting */
158 struct log_stream *
159 log_new_file(const char *path)
160 {
161   return do_log_new_file(path, 0);
162 }
163
164 int
165 log_switch(void)
166 {
167   time_t now = time(NULL);
168   struct tm *tm = localtime(&now);
169   ASSERT(tm);
170
171   int switched = 0;
172   for (int i=0; i < log_streams_after; i++)
173     if (log_streams.ptr[i]->handler == file_handler)
174       switched |= do_log_switch((struct file_stream *) log_streams.ptr[i], tm);
175   return switched;
176 }
177
178 void
179 log_switch_disable(void)
180 {
181   log_switch_nest++;
182 }
183
184 void
185 log_switch_enable(void)
186 {
187   ASSERT(log_switch_nest);
188   log_switch_nest--;
189 }
190
191 /* Emulate the old single-file interface: close the existing log file and open a new one. */
192 void
193 log_file(const char *name)
194 {
195   if (!name)
196     return;
197
198   struct log_stream *ls = do_log_new_file(name, FF_FD2_FOLLOWS);
199   struct log_stream *def = log_stream_by_flags(0);
200   log_rm_substream(def, NULL);
201   log_add_substream(def, ls);
202 }
203
204 #ifdef TEST
205
206 int main(int argc, char **argv)
207 {
208   log_init(argv[0]);
209   log_file("/proc/self/fd/1");
210   // struct log_stream *ls = log_new_fd(1);
211   // struct log_stream *ls = log_new_file("/tmp/quork-%Y%m%d-%H%M%S");
212   for (int i=1; i<argc; i++)
213     msg(L_INFO, argv[i]);
214   return 0;
215 }
216
217 #endif