]> mj.ucw.cz Git - libucw.git/blob - ucw/log-file.c
c74db01bea74535a0dbf8b8202701e19903c98f1
[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 /*
24  *  Use of the private fields of struct log_stream:
25  *
26  *      idata   file descriptor
27  *      udata   various flags (FF_xxx)
28  *      pdata   original name with strftime escapes
29  *      name    current name of the log file
30  *              (a dynamically allocated buffer)
31  */
32
33 enum log_file_flag {
34   FF_FORMAT_NAME = 1,           // Name contains strftime escapes
35   FF_CLOSE_FD = 2,              // Close the fd with the stream
36 };
37
38 #define MAX_EXPAND 64           // Maximum size of expansion of strftime escapes
39
40 static int log_switch_nest;
41
42 static void
43 do_log_reopen(struct log_stream *ls, const char *name)
44 {
45   int fd = ucw_open(name, O_WRONLY | O_CREAT | O_APPEND, 0666);
46   if (fd < 0)
47     die("Unable to open log file %s: %m", name);
48   if (ls->idata < 0)
49     ls->idata = fd;
50   else
51     {
52       dup2(fd, ls->idata);
53       close(fd);
54     }
55   if (ls->name)
56     {
57       xfree(ls->name);
58       ls->name = NULL;          // We have to keep this consistent, die() below can invoke logging
59     }
60   ls->name = xstrdup(name);
61 }
62
63 static int
64 do_log_switch(struct log_stream *ls, struct tm *tm)
65 {
66   if (!(ls->udata & FF_FORMAT_NAME))
67     {
68       if (ls->idata >= 0)
69         return 1;
70       else
71         {
72           do_log_reopen(ls, ls->pdata);
73           return 1;
74         }
75     }
76
77   int buflen = strlen(ls->pdata) + MAX_EXPAND;
78   char name[buflen];
79   int switched = 0;
80
81   ucwlib_lock();
82   if (!log_switch_nest)         // Avoid infinite loops if we die when switching logs
83     {
84       log_switch_nest++;
85       int l = strftime(name, buflen, ls->pdata, tm);
86       if (l < 0 || l >= buflen)
87         die("Error formatting log file name: %m");
88       if (!ls->name || strcmp(name, ls->name))
89         {
90           do_log_reopen(ls, name);
91           switched = 1;
92         }
93       log_switch_nest--;
94     }
95   ucwlib_unlock();
96   return switched;
97 }
98
99 /* Emulate the old single-file interface: close the existing log file and open a new one. */
100 void
101 log_file(const char *name)
102 {
103   if (!name)
104     return;
105
106   struct log_stream *ls = log_new_file(name);
107   struct log_stream *def = log_stream_by_flags(0);
108   simp_node *s;
109   while (s = clist_head(&def->substreams))
110     {
111       struct log_stream *old = s->p;
112       log_rm_substream(def, old);
113       if (old != LOG_STREAM_DEFAULT)
114         log_close_stream(old);
115     }
116   dup2(ls->idata, 2);                   // Let fd2 be an alias for the log file
117   log_add_substream(def, ls);
118 }
119
120 /* destructor for standard files */
121 static void
122 file_close(struct log_stream *ls)
123 {
124   if ((ls->udata & FF_CLOSE_FD) && ls->idata >= 0)
125     close(ls->idata);
126   xfree(ls->name);
127   xfree(ls->pdata);
128 }
129
130 /* handler for standard files */
131 static int
132 file_handler(struct log_stream *ls, struct log_msg *m)
133 {
134   if ((ls->udata & FF_FORMAT_NAME) && m->tm)
135     do_log_switch(ls, m->tm);
136
137   int r = write(ls->idata, m->m, m->m_len);
138   /* FIXME: check for errors here? */
139   return 0;
140 }
141
142 /* assign log to a file descriptor */
143 /* initialize with the default formatting, does NOT close the descriptor */
144 struct log_stream *
145 log_new_fd(int fd)
146 {
147   struct log_stream *ls = log_new_stream();
148   ls->idata = fd;
149   ls->msgfmt = LSFMT_DEFAULT;
150   ls->handler = file_handler;
151   ls->close = file_close;
152   ls->name = xmalloc(16);
153   snprintf(ls->name, 16, "fd%d", fd);
154   return ls;
155 }
156
157 /* open() a file (append mode) */
158 /* initialize with the default formatting */
159 struct log_stream *
160 log_new_file(const char *path)
161 {
162   struct log_stream *ls = log_new_stream();
163   ls->idata = -1;
164   ls->pdata = xstrdup(path);
165   if (strchr(path, '%'))
166     ls->udata = FF_FORMAT_NAME;
167   ls->udata |= FF_CLOSE_FD;
168   ls->msgfmt = LSFMT_DEFAULT;
169   ls->handler = file_handler;
170   ls->close = file_close;
171
172   time_t now = time(NULL);
173   struct tm *tm = localtime(&now);
174   ASSERT(tm);
175   do_log_switch(ls, tm);                // die()'s on errors
176   return ls;
177 }
178
179 int
180 log_switch(void)
181 {
182   time_t now = time(NULL);
183   struct tm *tm = localtime(&now);
184   ASSERT(tm);
185
186   int switched = 0;
187   for (int i=0; i < log_streams_after; i++)
188     if (log_streams.ptr[i]->handler == file_handler)
189       switched |= do_log_switch(log_streams.ptr[i], tm);
190   return switched;
191 }
192
193 void
194 log_switch_disable(void)
195 {
196   log_switch_nest++;
197 }
198
199 void
200 log_switch_enable(void)
201 {
202   ASSERT(log_switch_nest);
203   log_switch_nest--;
204 }
205
206 #ifdef TEST
207
208 int main(int argc, char **argv)
209 {
210   log_init(argv[0]);
211   log_file("/proc/self/fd/1");
212   // struct log_stream *ls = log_new_fd(1);
213   // struct log_stream *ls = log_new_file("/tmp/quork-%Y%m%d-%H%M%S");
214   for (int i=1; i<argc; i++)
215     msg(L_INFO, argv[i]);
216   return 0;
217 }
218
219 #endif