]> mj.ucw.cz Git - libucw.git/blob - ucw/log-file.c
Logging: Implemented rate limiting based on the Token Bucket Filter.
[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 #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   uns flags;                    // FF_xxx
29   char *orig_name;              // Original name with strftime escapes
30 };
31
32 enum log_file_flag {
33   FF_FORMAT_NAME = 1,           // Name contains strftime escapes
34   FF_CLOSE_FD = 2,              // Close the fd with the stream
35   FF_FD2_FOLLOWS = 4,           // Maintain stderr as a clone of this 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 file_stream *fs, 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 (fs->fd >= 0)
49     close(fs->fd);
50   fs->fd = fd;
51   if (fs->flags & FF_FD2_FOLLOWS)
52     dup2(fd, 2);
53   if (fs->ls.name)
54     {
55       xfree(fs->ls.name);
56       fs->ls.name = NULL;       // We have to keep the stream consistent -- die() below can invoke logging
57     }
58   fs->ls.name = xstrdup(name);
59 }
60
61 static int
62 do_log_switch(struct file_stream *fs, struct tm *tm)
63 {
64   if (!(fs->flags & FF_FORMAT_NAME))
65     {
66       if (fs->fd >= 0)
67         return 1;
68       else
69         {
70           do_log_reopen(fs, fs->orig_name);
71           return 1;
72         }
73     }
74
75   int buflen = strlen(fs->orig_name) + MAX_EXPAND;
76   char name[buflen];
77   int switched = 0;
78
79   ucwlib_lock();
80   if (!log_switch_nest)         // Avoid infinite loops if we die when switching logs
81     {
82       log_switch_nest++;
83       int l = strftime(name, buflen, fs->orig_name, tm);
84       if (l < 0 || l >= buflen)
85         die("Error formatting log file name: %m");
86       if (!fs->ls.name || strcmp(name, fs->ls.name))
87         {
88           do_log_reopen(fs, name);
89           switched = 1;
90         }
91       log_switch_nest--;
92     }
93   ucwlib_unlock();
94   return switched;
95 }
96
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 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   return ((r < 0) ? errno : 0);
116 }
117
118 struct log_stream *
119 log_new_fd(int fd)
120 {
121   struct log_stream *ls = log_new_stream(sizeof(struct file_stream));
122   struct file_stream *fs = (struct file_stream *) ls;
123   fs->fd = fd;
124   ls->msgfmt = LSFMT_DEFAULT;
125   ls->handler = file_handler;
126   ls->close = file_close;
127   ls->name = xmalloc(16);
128   snprintf(ls->name, 16, "fd%d", fd);
129   return ls;
130 }
131
132 static struct log_stream *
133 do_log_new_file(const char *path, uns more_flags)
134 {
135   struct log_stream *ls = log_new_stream(sizeof(struct file_stream));
136   struct file_stream *fs = (struct file_stream *) ls;
137   fs->fd = -1;
138   fs->orig_name = xstrdup(path);
139   if (strchr(path, '%'))
140     fs->flags = FF_FORMAT_NAME;
141   fs->flags |= FF_CLOSE_FD | more_flags;
142   ls->msgfmt = LSFMT_DEFAULT;
143   ls->handler = file_handler;
144   ls->close = file_close;
145
146   time_t now = time(NULL);
147   struct tm *tm = localtime(&now);
148   ASSERT(tm);
149   do_log_switch(fs, tm);                // die()'s on errors
150   return ls;
151 }
152
153 struct log_stream *
154 log_new_file(const char *path)
155 {
156   return do_log_new_file(path, 0);
157 }
158
159 int
160 log_switch(void)
161 {
162   time_t now = time(NULL);
163   struct tm *tm = localtime(&now);
164   ASSERT(tm);
165
166   int switched = 0;
167   for (int i=0; i < log_streams_after; i++)
168     if (log_streams.ptr[i]->handler == file_handler)
169       switched |= do_log_switch((struct file_stream *) log_streams.ptr[i], tm);
170   return switched;
171 }
172
173 void
174 log_switch_disable(void)
175 {
176   log_switch_nest++;
177 }
178
179 void
180 log_switch_enable(void)
181 {
182   ASSERT(log_switch_nest);
183   log_switch_nest--;
184 }
185
186 void
187 log_file(const char *name)
188 {
189   if (!name)
190     return;
191
192   struct log_stream *ls = do_log_new_file(name, FF_FD2_FOLLOWS);
193   struct log_stream *def = log_stream_by_flags(0);
194   log_rm_substream(def, NULL);
195   log_add_substream(def, ls);
196   log_close_stream(ls);
197 }
198
199 #ifdef TEST
200
201 int main(int argc, char **argv)
202 {
203   log_init(argv[0]);
204   log_file("/proc/self/fd/1");
205   // struct log_stream *ls = log_new_fd(1);
206   // struct log_stream *ls = log_new_file("/tmp/quork-%Y%m%d-%H%M%S");
207   for (int i=1; i<argc; i++)
208     msg(L_INFO, argv[i]);
209   log_close_all();
210   return 0;
211 }
212
213 #endif