]> mj.ucw.cz Git - libucw.git/blob - ucw/log-file.c
Logging: Adapted the `logger' utility to the new numbering of levels.
[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 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 static int
107 file_handler(struct log_stream *ls, struct log_msg *m)
108 {
109   struct file_stream *fs = (struct file_stream *) ls;
110   if ((fs->flags & FF_FORMAT_NAME) && m->tm)
111     do_log_switch(fs, m->tm);
112
113   int r = write(fs->fd, m->m, m->m_len);
114   /* FIXME: check for errors here? */
115   return 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