]> mj.ucw.cz Git - libucw.git/blob - ucw/log-file.c
Released as 6.5.16.
[libucw.git] / ucw / log-file.c
1 /*
2  *      UCW Library -- Logging to Files
3  *
4  *      (c) 1997--2015 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/io.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 #include <errno.h>
24
25 struct file_stream {
26   struct log_stream ls;         // ls.name is the current name of the log file
27   int fd;
28   uint flags;                   // FF_xxx
29   char *orig_name;              // Original name with strftime escapes
30 };
31
32 #define MAX_EXPAND 64           // Maximum size of expansion of strftime escapes
33
34 static int log_switch_nest;
35 static bool log_stderr_replaced;
36
37 static void
38 do_log_reopen(struct file_stream *fs, const char *name)
39 {
40   int fd = ucw_open(name, O_WRONLY | O_CREAT | O_APPEND, 0666);
41   if (fd < 0)
42     die("Unable to open log file %s: %m", name);
43   if (fs->fd >= 0)
44     close(fs->fd);
45   fs->fd = fd;
46   if (fs->flags & FF_FD2_FOLLOWS)
47     {
48       dup2(fd, 2);
49       log_stderr_replaced = 1;
50     }
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 static void
96 file_close(struct log_stream *ls)
97 {
98   struct file_stream *fs = (struct file_stream *) ls;
99   if ((fs->flags & FF_CLOSE_FD) && fs->fd >= 0)
100     close(fs->fd);
101   xfree(fs->ls.name);
102   xfree(fs->orig_name);
103 }
104
105 static int
106 file_handler(struct log_stream *ls, struct log_msg *m)
107 {
108   struct file_stream *fs = (struct file_stream *) ls;
109   if ((fs->flags & FF_FORMAT_NAME) && m->tm)
110     do_log_switch(fs, m->tm);
111
112   int r = write(fs->fd, m->m, m->m_len);
113   return ((r < 0) ? errno : 0);
114 }
115
116 struct log_stream *
117 log_new_fd(int fd, uint flags)
118 {
119   struct log_stream *ls = log_new_stream(sizeof(struct file_stream));
120   struct file_stream *fs = (struct file_stream *) ls;
121   fs->fd = fd;
122   if (fd == 2)
123     log_stderr_replaced = 1;
124   fs->flags = flags;
125   ls->msgfmt = LSFMT_DEFAULT;
126   ls->handler = file_handler;
127   ls->close = file_close;
128   ls->name = xmalloc(16);
129   snprintf(ls->name, 16, "fd%d", fd);
130   return ls;
131 }
132
133 struct log_stream *
134 log_new_file(const char *path, uint flags)
135 {
136   struct log_stream *ls = log_new_stream(sizeof(struct file_stream));
137   struct file_stream *fs = (struct file_stream *) ls;
138   fs->fd = -1;
139   fs->flags = FF_CLOSE_FD | flags;
140   fs->orig_name = xstrdup(path);
141   if (strchr(path, '%'))
142     fs->flags |= FF_FORMAT_NAME;
143   ls->msgfmt = LSFMT_DEFAULT;
144   ls->handler = file_handler;
145   ls->close = file_close;
146
147   time_t now = time(NULL);
148   struct tm *tm = localtime(&now);
149   ASSERT(tm);
150   do_log_switch(fs, tm);                // die()'s on errors
151   return ls;
152 }
153
154 int
155 log_switch(void)
156 {
157   time_t now = time(NULL);
158   struct tm *tm = localtime(&now);
159   ASSERT(tm);
160
161   int switched = 0;
162   for (int i=0; i < log_streams_after; i++)
163     if (log_streams.ptr[i]->handler == file_handler)
164       switched |= do_log_switch((struct file_stream *) log_streams.ptr[i], tm);
165   return switched;
166 }
167
168 void
169 log_switch_disable(void)
170 {
171   log_switch_nest++;
172 }
173
174 void
175 log_switch_enable(void)
176 {
177   ASSERT(log_switch_nest);
178   log_switch_nest--;
179 }
180
181 void
182 log_file(const char *name)
183 {
184   if (!name)
185     return;
186
187   log_set_default_stream(log_new_file(name, FF_FD2_FOLLOWS));
188 }
189
190 void
191 log_drop_stderr(void)
192 {
193   if (!log_stderr_replaced)
194     {
195       if (dup2(1, 2) < 0)
196         die("Cannot get rid of stderr: %m");
197       log_stderr_replaced = 1;
198     }
199 }
200
201 #ifdef TEST
202
203 int main(int argc, char **argv)
204 {
205   log_init(argv[0]);
206   log_file("/proc/self/fd/1");
207   // struct log_stream *ls = log_new_fd(1, 0);
208   // struct log_stream *ls = log_new_file("/tmp/quork-%Y%m%d-%H%M%S", 0);
209   for (int i=1; i<argc; i++)
210     msg(L_INFO, argv[i]);
211   log_close_all();
212   return 0;
213 }
214
215 #endif