Added a simple access logger.
[ashd.git] / src / accesslog.c
1 /*
2     ashd - A Sane HTTP Daemon
3     Copyright (C) 2008  Fredrik Tolf <fredrik@dolda2000.com>
4
5     This program is free software: you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation, either version 3 of the License, or
8     (at your option) any later version.
9
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14
15     You should have received a copy of the GNU General Public License
16     along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include <stdlib.h>
20 #include <stdio.h>
21 #include <unistd.h>
22 #include <string.h>
23 #include <errno.h>
24 #include <sys/poll.h>
25 #include <time.h>
26 #include <sys/time.h>
27
28 #ifdef HAVE_CONFIG_H
29 #include <config.h>
30 #endif
31 #include <utils.h>
32 #include <log.h>
33 #include <req.h>
34 #include <proc.h>
35
36 #define DEFFORMAT "%{%Y-%m-%d %H:%M:%S}t %m %u %A \"%G\""
37
38 static int ch;
39 static FILE *out;
40 static int flush = 1;
41 static char *format;
42 static time_t now;
43
44 static void qputs(char *s, FILE *o)
45 {
46     for(; *s; s++) {
47         if(*s == '\"') {
48             fputs("\\\"", o);
49         } else if(*s == '\\') {
50             fputs("\\\\", o);
51         } else if(*s == '\n') {
52             fputs("\\n", o);
53         } else if(*s == '\t') {
54             fputs("\\t", o);
55         } else if((*s < 32) || (*s >= 128)) {
56             fprintf(o, "\\x%02x", *s);
57         } else {
58             fputc(*s, o);
59         }
60     }
61 }
62
63 static void logitem(struct hthead *req, char o, char *d)
64 {
65     char *h, *p;
66     char buf[1024];
67     struct timeval tv;
68     
69     switch(o) {
70     case '%':
71         putc('%', out);
72         break;
73     case 'h':
74         if((h = getheader(req, d)) == NULL) {
75             putc('-', out);
76         } else {
77             qputs(h, out);
78         }
79         break;
80     case 'u':
81         qputs(req->url, out);
82         break;
83     case 'U':
84         strcpy(buf, req->url);
85         if((p = strchr(buf, '?')) != NULL)
86             *p = 0;
87         qputs(buf, out);
88         break;
89     case 'm':
90         qputs(req->method, out);
91         break;
92     case 'r':
93         qputs(req->rest, out);
94         break;
95     case 'v':
96         qputs(req->ver, out);
97         break;
98     case 't':
99         if(!*d)
100             d = "%a, %d %b %Y %H:%M:%S %z";
101         strftime(buf, sizeof(buf), d, localtime(&now));
102         qputs(buf, out);
103         break;
104     case 'T':
105         if(!*d)
106             d = "%a, %d %b %Y %H:%M:%S %z";
107         strftime(buf, sizeof(buf), d, gmtime(&now));
108         qputs(buf, out);
109         break;
110     case 's':
111         gettimeofday(&tv, NULL);
112         fprintf(out, "%06i", (int)tv.tv_usec);
113         break;
114     case 'A':
115         logitem(req, 'h', "X-Ash-Address");
116         break;
117     case 'H':
118         logitem(req, 'h', "Host");
119         break;
120     case 'R':
121         logitem(req, 'h', "Referer");
122         break;
123     case 'G':
124         logitem(req, 'h', "User-Agent");
125         break;
126     }
127 }
128
129 static void logreq(struct hthead *req)
130 {
131     char *p, *p2;
132     char d[strlen(format)];
133     char o;
134     
135     p = format;
136     while(*p) {
137         if(*p == '%') {
138             p++;
139             if(*p == '{') {
140                 p++;
141                 if((p2 = strchr(p, '}')) == NULL)
142                     continue;
143                 memcpy(d, p, p2 - p);
144                 d[p2 - p] = 0;
145                 p = p2 + 1;
146             } else {
147                 d[0] = 0;
148             }
149             o = *p++;
150             if(o == 0)
151                 break;
152             logitem(req, o, d);
153         } else {
154             fputc(*p++, out);
155         }
156     }
157     fputc('\n', out);
158     if(flush)
159         fflush(out);
160 }
161
162 static void serve(struct hthead *req, int fd)
163 {
164     now = time(NULL);
165     if(sendreq(ch, req, fd)) {
166         flog(LOG_ERR, "accesslog: could not pass request to child: %s", strerror(errno));
167         exit(1);
168     }
169     logreq(req);
170 }
171
172 static void usage(FILE *out)
173 {
174     fprintf(out, "usage: accesslog [-hFa] [-f FORMAT] [-o OUTFILE] CHILD [ARGS...]\n");
175 }
176
177 int main(int argc, char **argv)
178 {
179     int c, ret;
180     struct hthead *req;
181     int fd;
182     struct pollfd pfd[2];
183     char *outfile;
184     
185     optarg = NULL;
186     while((c = getopt(argc, argv, "+hFaf:o:")) >= 0) {
187         switch(c) {
188         case 'h':
189             usage(stdout);
190             exit(0);
191         case 'o':
192             outfile = optarg;
193             break;
194         case 'F':
195             flush = 0;
196             break;
197         case 'f':
198             format = optarg;
199             break;
200         case 'a':
201             format = "%A - - [%{%d/%b/%Y:%H:%M:%S %z}t] \"%m %u %v\" - - \"%R\" \"%G\"";
202             break;
203         default:
204             usage(stderr);
205             exit(1);
206         }
207     }
208     if(optind >= argc) {
209         usage(stderr);
210         exit(1);
211     }
212     if(format == NULL)
213         format = DEFFORMAT;
214     if(outfile != NULL) {
215         if((out = fopen(outfile, "a")) == NULL) {
216             flog(LOG_ERR, "accesslog: could not open %s for logging: %s", outfile, strerror(errno));
217             exit(1);
218         }
219     } else {
220         out = stdout;
221     }
222     if((ch = stdmkchild(argv + optind, NULL, NULL)) < 0) {
223         flog(LOG_ERR, "accesslog: could fork child: %s", strerror(errno));
224         exit(1);
225     }
226     while(1) {
227         memset(pfd, 0, sizeof(pfd));
228         pfd[0].fd = 0;
229         pfd[0].events = POLLIN;
230         pfd[1].fd = ch;
231         pfd[1].events = POLLHUP;
232         if((ret = poll(pfd, 2, -1)) < 0) {
233             if(errno != EINTR) {
234                 flog(LOG_ERR, "accesslog: error in poll: %s", strerror(errno));
235                 exit(1);
236             }
237         }
238         if(pfd[0].revents) {
239             if((fd = recvreq(0, &req)) < 0) {
240                 if(errno == 0)
241                     break;
242                 flog(LOG_ERR, "accesslog: error in recvreq: %s", strerror(errno));
243                 exit(1);
244             }
245             serve(req, fd);
246             freehthead(req);
247             close(fd);
248         }
249         if(pfd[1].revents & POLLHUP)
250             break;
251     }
252     return(0);
253 }