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