[mod_accesslog] don't close fd -1
[lighttpd.git] / src / mod_accesslog.c
blob39595c942730dab2b8f5559e855a76d3d5817d34
1 #include "base.h"
2 #include "log.h"
3 #include "buffer.h"
5 #include "plugin.h"
7 #include "inet_ntop_cache.h"
9 #include "sys-socket.h"
11 #include <sys/types.h>
12 #include <sys/stat.h>
14 #include <ctype.h>
15 #include <stdlib.h>
16 #include <string.h>
17 #include <fcntl.h>
18 #include <unistd.h>
19 #include <errno.h>
20 #include <time.h>
22 #include <stdio.h>
24 #ifdef HAVE_SYSLOG_H
25 # include <syslog.h>
26 #endif
28 typedef struct {
29 char key;
30 enum {
31 FORMAT_UNSET,
32 FORMAT_UNSUPPORTED,
33 FORMAT_PERCENT,
34 FORMAT_REMOTE_HOST,
35 FORMAT_REMOTE_IDENT,
36 FORMAT_REMOTE_USER,
37 FORMAT_TIMESTAMP,
38 FORMAT_REQUEST_LINE,
39 FORMAT_STATUS,
40 FORMAT_BYTES_OUT_NO_HEADER,
41 FORMAT_HEADER,
43 FORMAT_REMOTE_ADDR,
44 FORMAT_LOCAL_ADDR,
45 FORMAT_COOKIE,
46 FORMAT_TIME_USED_MS,
47 FORMAT_ENV,
48 FORMAT_FILENAME,
49 FORMAT_REQUEST_PROTOCOL,
50 FORMAT_REQUEST_METHOD,
51 FORMAT_SERVER_PORT,
52 FORMAT_QUERY_STRING,
53 FORMAT_TIME_USED,
54 FORMAT_URL,
55 FORMAT_SERVER_NAME,
56 FORMAT_HTTP_HOST,
57 FORMAT_CONNECTION_STATUS,
58 FORMAT_BYTES_IN,
59 FORMAT_BYTES_OUT,
61 FORMAT_RESPONSE_HEADER
62 } type;
63 } format_mapping;
65 /**
68 * "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\""
72 static const format_mapping fmap[] =
74 { '%', FORMAT_PERCENT },
75 { 'h', FORMAT_REMOTE_HOST },
76 { 'l', FORMAT_REMOTE_IDENT },
77 { 'u', FORMAT_REMOTE_USER },
78 { 't', FORMAT_TIMESTAMP },
79 { 'r', FORMAT_REQUEST_LINE },
80 { 's', FORMAT_STATUS },
81 { 'b', FORMAT_BYTES_OUT_NO_HEADER },
82 { 'i', FORMAT_HEADER },
84 { 'a', FORMAT_REMOTE_ADDR },
85 { 'A', FORMAT_LOCAL_ADDR },
86 { 'B', FORMAT_BYTES_OUT_NO_HEADER },
87 { 'C', FORMAT_COOKIE },
88 { 'D', FORMAT_TIME_USED_MS },
89 { 'e', FORMAT_ENV },
90 { 'f', FORMAT_FILENAME },
91 { 'H', FORMAT_REQUEST_PROTOCOL },
92 { 'm', FORMAT_REQUEST_METHOD },
93 { 'n', FORMAT_UNSUPPORTED }, /* we have no notes */
94 { 'p', FORMAT_SERVER_PORT },
95 { 'P', FORMAT_UNSUPPORTED }, /* we are only one process */
96 { 'q', FORMAT_QUERY_STRING },
97 { 'T', FORMAT_TIME_USED },
98 { 'U', FORMAT_URL }, /* w/o querystring */
99 { 'v', FORMAT_SERVER_NAME },
100 { 'V', FORMAT_HTTP_HOST },
101 { 'X', FORMAT_CONNECTION_STATUS },
102 { 'I', FORMAT_BYTES_IN },
103 { 'O', FORMAT_BYTES_OUT },
105 { 'o', FORMAT_RESPONSE_HEADER },
107 { '\0', FORMAT_UNSET }
111 typedef struct {
112 enum { FIELD_UNSET, FIELD_STRING, FIELD_FORMAT } type;
114 buffer *string;
115 int field;
116 } format_field;
118 typedef struct {
119 format_field **ptr;
121 size_t used;
122 size_t size;
123 } format_fields;
125 typedef struct {
126 buffer *access_logfile;
127 int log_access_fd;
128 buffer *access_logbuffer; /* each logfile has a separate buffer */
130 unsigned short use_syslog; /* syslog has global buffer */
131 unsigned short syslog_level;
133 buffer *format;
135 time_t last_generated_accesslog_ts;
136 time_t *last_generated_accesslog_ts_ptr;
138 buffer *ts_accesslog_str;
139 buffer *ts_accesslog_fmt_str;
140 unsigned short append_tz_offset;
142 format_fields *parsed_format;
143 } plugin_config;
145 typedef struct {
146 PLUGIN_DATA;
148 plugin_config **config_storage;
149 plugin_config conf;
151 buffer *syslog_logbuffer; /* syslog has global buffer. no caching, always written directly */
152 } plugin_data;
154 INIT_FUNC(mod_accesslog_init) {
155 plugin_data *p;
157 p = calloc(1, sizeof(*p));
158 p->syslog_logbuffer = buffer_init();
160 return p;
163 static void accesslog_append_escaped(buffer *dest, buffer *str) {
164 char *ptr, *start, *end;
166 /* replaces non-printable chars with \xHH where HH is the hex representation of the byte */
167 /* exceptions: " => \", \ => \\, whitespace chars => \n \t etc. */
168 if (str->used == 0) return;
169 buffer_prepare_append(dest, str->used - 1);
171 for (ptr = start = str->ptr, end = str->ptr + str->used - 1; ptr < end; ptr++) {
172 char const c = *ptr;
173 if (c >= ' ' && c <= '~' && c != '"' && c != '\\') {
174 /* nothing to change, add later as one block */
175 } else {
176 /* copy previous part */
177 if (start < ptr) {
178 buffer_append_string_len(dest, start, ptr - start);
180 start = ptr + 1;
182 switch (c) {
183 case '"':
184 BUFFER_APPEND_STRING_CONST(dest, "\\\"");
185 break;
186 case '\\':
187 BUFFER_APPEND_STRING_CONST(dest, "\\\\");
188 break;
189 case '\b':
190 BUFFER_APPEND_STRING_CONST(dest, "\\b");
191 break;
192 case '\n':
193 BUFFER_APPEND_STRING_CONST(dest, "\\n");
194 break;
195 case '\r':
196 BUFFER_APPEND_STRING_CONST(dest, "\\r");
197 break;
198 case '\t':
199 BUFFER_APPEND_STRING_CONST(dest, "\\t");
200 break;
201 case '\v':
202 BUFFER_APPEND_STRING_CONST(dest, "\\v");
203 break;
204 default: {
205 /* non printable char => \xHH */
206 char hh[5] = {'\\','x',0,0,0};
207 char h = c / 16;
208 hh[2] = (h > 9) ? (h - 10 + 'A') : (h + '0');
209 h = c % 16;
210 hh[3] = (h > 9) ? (h - 10 + 'A') : (h + '0');
211 buffer_append_string_len(dest, &hh[0], 4);
213 break;
218 if (start < end) {
219 buffer_append_string_len(dest, start, end - start);
223 static int accesslog_parse_format(server *srv, format_fields *fields, buffer *format) {
224 size_t i, j, k = 0, start = 0;
226 if (format->used == 0) return -1;
228 for (i = 0; i < format->used - 1; i++) {
229 switch(format->ptr[i]) {
230 case '%':
231 if (i > 0 && start != i) {
232 /* copy the string before this % */
233 if (fields->size == 0) {
234 fields->size = 16;
235 fields->used = 0;
236 fields->ptr = malloc(fields->size * sizeof(format_field * ));
237 } else if (fields->used == fields->size) {
238 fields->size += 16;
239 fields->ptr = realloc(fields->ptr, fields->size * sizeof(format_field * ));
242 fields->ptr[fields->used] = malloc(sizeof(format_field));
243 fields->ptr[fields->used]->type = FIELD_STRING;
244 fields->ptr[fields->used]->string = buffer_init();
246 buffer_copy_string_len(fields->ptr[fields->used]->string, format->ptr + start, i - start);
248 fields->used++;
251 /* we need a new field */
253 if (fields->size == 0) {
254 fields->size = 16;
255 fields->used = 0;
256 fields->ptr = malloc(fields->size * sizeof(format_field * ));
257 } else if (fields->used == fields->size) {
258 fields->size += 16;
259 fields->ptr = realloc(fields->ptr, fields->size * sizeof(format_field * ));
262 /* search for the terminating command */
263 switch (format->ptr[i+1]) {
264 case '>':
265 case '<':
266 /* after the } has to be a character */
267 if (format->ptr[i+2] == '\0') {
268 log_error_write(srv, __FILE__, __LINE__, "s", "%< and %> have to be followed by a format-specifier");
269 return -1;
273 for (j = 0; fmap[j].key != '\0'; j++) {
274 if (fmap[j].key != format->ptr[i+2]) continue;
276 /* found key */
278 fields->ptr[fields->used] = malloc(sizeof(format_field));
279 fields->ptr[fields->used]->type = FIELD_FORMAT;
280 fields->ptr[fields->used]->field = fmap[j].type;
281 fields->ptr[fields->used]->string = NULL;
283 fields->used++;
285 break;
288 if (fmap[j].key == '\0') {
289 log_error_write(srv, __FILE__, __LINE__, "s", "%< and %> have to be followed by a valid format-specifier");
290 return -1;
293 start = i + 3;
294 i = start - 1; /* skip the string */
296 break;
297 case '{':
298 /* go forward to } */
300 for (k = i+2; k < format->used - 1; k++) {
301 if (format->ptr[k] == '}') break;
304 if (k == format->used - 1) {
305 log_error_write(srv, __FILE__, __LINE__, "s", "%{ has to be terminated by a }");
306 return -1;
309 /* after the } has to be a character */
310 if (format->ptr[k+1] == '\0') {
311 log_error_write(srv, __FILE__, __LINE__, "s", "%{...} has to be followed by a format-specifier");
312 return -1;
315 if (k == i + 2) {
316 log_error_write(srv, __FILE__, __LINE__, "s", "%{...} has to be contain a string");
317 return -1;
320 for (j = 0; fmap[j].key != '\0'; j++) {
321 if (fmap[j].key != format->ptr[k+1]) continue;
323 /* found key */
325 fields->ptr[fields->used] = malloc(sizeof(format_field));
326 fields->ptr[fields->used]->type = FIELD_FORMAT;
327 fields->ptr[fields->used]->field = fmap[j].type;
328 fields->ptr[fields->used]->string = buffer_init();
330 buffer_copy_string_len(fields->ptr[fields->used]->string, format->ptr + i + 2, k - (i + 2));
332 fields->used++;
334 break;
337 if (fmap[j].key == '\0') {
338 log_error_write(srv, __FILE__, __LINE__, "s", "%{...} has to be followed by a valid format-specifier");
339 return -1;
342 start = k + 2;
343 i = start - 1; /* skip the string */
345 break;
346 default:
347 /* after the % has to be a character */
348 if (format->ptr[i+1] == '\0') {
349 log_error_write(srv, __FILE__, __LINE__, "s", "% has to be followed by a format-specifier");
350 return -1;
353 for (j = 0; fmap[j].key != '\0'; j++) {
354 if (fmap[j].key != format->ptr[i+1]) continue;
356 /* found key */
358 fields->ptr[fields->used] = malloc(sizeof(format_field));
359 fields->ptr[fields->used]->type = FIELD_FORMAT;
360 fields->ptr[fields->used]->field = fmap[j].type;
361 fields->ptr[fields->used]->string = NULL;
363 fields->used++;
365 break;
368 if (fmap[j].key == '\0') {
369 log_error_write(srv, __FILE__, __LINE__, "s", "% has to be followed by a valid format-specifier");
370 return -1;
373 start = i + 2;
374 i = start - 1; /* skip the string */
376 break;
379 break;
383 if (start < i) {
384 /* copy the string */
385 if (fields->size == 0) {
386 fields->size = 16;
387 fields->used = 0;
388 fields->ptr = malloc(fields->size * sizeof(format_field * ));
389 } else if (fields->used == fields->size) {
390 fields->size += 16;
391 fields->ptr = realloc(fields->ptr, fields->size * sizeof(format_field * ));
394 fields->ptr[fields->used] = malloc(sizeof(format_field));
395 fields->ptr[fields->used]->type = FIELD_STRING;
396 fields->ptr[fields->used]->string = buffer_init();
398 buffer_copy_string_len(fields->ptr[fields->used]->string, format->ptr + start, i - start);
400 fields->used++;
403 return 0;
406 FREE_FUNC(mod_accesslog_free) {
407 plugin_data *p = p_d;
408 size_t i;
410 if (!p) return HANDLER_GO_ON;
412 if (p->config_storage) {
414 for (i = 0; i < srv->config_context->used; i++) {
415 plugin_config *s = p->config_storage[i];
417 if (!s) continue;
419 if (s->access_logbuffer->used) {
420 if (s->log_access_fd != -1) {
421 write(s->log_access_fd, s->access_logbuffer->ptr, s->access_logbuffer->used - 1);
425 if (s->log_access_fd != -1) close(s->log_access_fd);
427 buffer_free(s->ts_accesslog_str);
428 buffer_free(s->ts_accesslog_fmt_str);
429 buffer_free(s->access_logbuffer);
430 buffer_free(s->format);
431 buffer_free(s->access_logfile);
433 if (s->parsed_format) {
434 size_t j;
435 for (j = 0; j < s->parsed_format->used; j++) {
436 if (s->parsed_format->ptr[j]->string) buffer_free(s->parsed_format->ptr[j]->string);
437 free(s->parsed_format->ptr[j]);
439 free(s->parsed_format->ptr);
440 free(s->parsed_format);
443 free(s);
446 free(p->config_storage);
449 if (p->syslog_logbuffer) buffer_free(p->syslog_logbuffer);
450 free(p);
452 return HANDLER_GO_ON;
455 SETDEFAULTS_FUNC(log_access_open) {
456 plugin_data *p = p_d;
457 size_t i = 0;
459 config_values_t cv[] = {
460 { "accesslog.filename", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
461 { "accesslog.use-syslog", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION },
462 { "accesslog.format", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
463 { "accesslog.syslog-level", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION },
464 { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
467 if (!p) return HANDLER_ERROR;
469 p->config_storage = calloc(1, srv->config_context->used * sizeof(plugin_config *));
471 for (i = 0; i < srv->config_context->used; i++) {
472 plugin_config *s;
474 s = calloc(1, sizeof(plugin_config));
475 s->access_logfile = buffer_init();
476 s->format = buffer_init();
477 s->access_logbuffer = buffer_init();
478 s->ts_accesslog_str = buffer_init();
479 s->ts_accesslog_fmt_str = buffer_init();
480 s->log_access_fd = -1;
481 s->last_generated_accesslog_ts = 0;
482 s->last_generated_accesslog_ts_ptr = &(s->last_generated_accesslog_ts);
483 s->syslog_level = LOG_INFO;
486 cv[0].destination = s->access_logfile;
487 cv[1].destination = &(s->use_syslog);
488 cv[2].destination = s->format;
489 cv[3].destination = &(s->syslog_level);
491 p->config_storage[i] = s;
493 if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) {
494 return HANDLER_ERROR;
497 if (i == 0 && buffer_is_empty(s->format)) {
498 /* set a default logfile string */
500 buffer_copy_string_len(s->format, CONST_STR_LEN("%h %V %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\""));
503 /* parse */
505 if (s->format->used) {
506 size_t j, count;
508 s->parsed_format = calloc(1, sizeof(*(s->parsed_format)));
510 if (-1 == accesslog_parse_format(srv, s->parsed_format, s->format)) {
512 log_error_write(srv, __FILE__, __LINE__, "sb",
513 "parsing accesslog-definition failed:", s->format);
515 return HANDLER_ERROR;
518 /* make sure they didn't try to send the timestamp in twice...
519 * also, save the format string in a different variable (this
520 * will save a few conditionals later)
522 count = 0;
523 for (j = 0; j < s->parsed_format->used; j++) {
524 if (FIELD_FORMAT == s->parsed_format->ptr[j]->type) {
525 if (FORMAT_TIMESTAMP == s->parsed_format->ptr[j]->field) {
526 if (!buffer_is_empty(s->parsed_format->ptr[j]->string)) {
527 buffer_copy_string(s->ts_accesslog_fmt_str, s->parsed_format->ptr[j]->string->ptr);
530 if (++count > 1) {
531 log_error_write(srv, __FILE__, __LINE__, "sb",
532 "you may not use the timestamp twice in the same access log:", s->format);
534 return HANDLER_ERROR;
540 #if 0
541 /* debugging */
542 for (j = 0; j < s->parsed_format->used; j++) {
543 switch (s->parsed_format->ptr[j]->type) {
544 case FIELD_FORMAT:
545 log_error_write(srv, __FILE__, __LINE__, "ssds",
546 "config:", "format", s->parsed_format->ptr[j]->field,
547 s->parsed_format->ptr[j]->string ?
548 s->parsed_format->ptr[j]->string->ptr : "" );
549 break;
550 case FIELD_STRING:
551 log_error_write(srv, __FILE__, __LINE__, "ssbs", "config:", "string '", s->parsed_format->ptr[j]->string, "'");
552 break;
553 default:
554 break;
557 #endif
560 s->append_tz_offset = 0;
561 if (buffer_is_empty(s->ts_accesslog_fmt_str)) {
562 #if defined(HAVE_STRUCT_TM_GMTOFF)
563 BUFFER_COPY_STRING_CONST(s->ts_accesslog_fmt_str, "[%d/%b/%Y:%H:%M:%S ");
564 s->append_tz_offset = 1;
565 #else
566 BUFFER_COPY_STRING_CONST(s->ts_accesslog_fmt_str, "[%d/%b/%Y:%H:%M:%S +0000]");
567 #endif
570 if (s->use_syslog) {
571 /* ignore the next checks */
572 continue;
575 if (s->access_logfile->used < 2) continue;
577 if (-1 == (s->log_access_fd = open_logfile_or_pipe(srv, s->access_logfile->ptr)))
578 return HANDLER_ERROR;
582 return HANDLER_GO_ON;
585 SIGHUP_FUNC(log_access_cycle) {
586 plugin_data *p = p_d;
587 size_t i;
589 if (!p->config_storage) return HANDLER_GO_ON;
591 for (i = 0; i < srv->config_context->used; i++) {
592 plugin_config *s = p->config_storage[i];
594 if (s->access_logbuffer->used) {
595 if (s->log_access_fd != -1) {
596 write(s->log_access_fd, s->access_logbuffer->ptr, s->access_logbuffer->used - 1);
599 buffer_reset(s->access_logbuffer);
602 if (s->use_syslog == 0 &&
603 s->access_logfile->used > 1 &&
604 s->access_logfile->ptr[0] != '|') {
606 if (-1 != s->log_access_fd) close(s->log_access_fd);
608 if (-1 == (s->log_access_fd =
609 open(s->access_logfile->ptr, O_APPEND | O_WRONLY | O_CREAT | O_LARGEFILE, 0644))) {
611 log_error_write(srv, __FILE__, __LINE__, "ss", "cycling access-log failed:", strerror(errno));
613 return HANDLER_ERROR;
615 fd_close_on_exec(s->log_access_fd);
619 return HANDLER_GO_ON;
622 #define PATCH(x) \
623 p->conf.x = s->x;
624 static int mod_accesslog_patch_connection(server *srv, connection *con, plugin_data *p) {
625 size_t i, j;
626 plugin_config *s = p->config_storage[0];
628 PATCH(access_logfile);
629 PATCH(format);
630 PATCH(log_access_fd);
631 PATCH(last_generated_accesslog_ts_ptr);
632 PATCH(access_logbuffer);
633 PATCH(ts_accesslog_str);
634 PATCH(ts_accesslog_fmt_str);
635 PATCH(append_tz_offset);
636 PATCH(parsed_format);
637 PATCH(use_syslog);
638 PATCH(syslog_level);
640 /* skip the first, the global context */
641 for (i = 1; i < srv->config_context->used; i++) {
642 data_config *dc = (data_config *)srv->config_context->data[i];
643 s = p->config_storage[i];
645 /* condition didn't match */
646 if (!config_check_cond(srv, con, dc)) continue;
648 /* merge config */
649 for (j = 0; j < dc->value->used; j++) {
650 data_unset *du = dc->value->data[j];
652 if (buffer_is_equal_string(du->key, CONST_STR_LEN("accesslog.filename"))) {
653 PATCH(access_logfile);
654 PATCH(log_access_fd);
655 PATCH(access_logbuffer);
656 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("accesslog.format"))) {
657 PATCH(format);
658 PATCH(parsed_format);
659 PATCH(last_generated_accesslog_ts_ptr);
660 PATCH(ts_accesslog_str);
661 PATCH(ts_accesslog_fmt_str);
662 PATCH(append_tz_offset);
663 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("accesslog.use-syslog"))) {
664 PATCH(use_syslog);
665 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("accesslog.syslog-level"))) {
666 PATCH(syslog_level);
671 return 0;
673 #undef PATCH
675 REQUESTDONE_FUNC(log_access_write) {
676 plugin_data *p = p_d;
677 buffer *b;
678 size_t j;
680 int newts = 0;
681 data_string *ds;
683 mod_accesslog_patch_connection(srv, con, p);
685 /* No output device, nothing to do */
686 if (!p->conf.use_syslog && p->conf.log_access_fd == -1) return HANDLER_GO_ON;
688 if (p->conf.use_syslog) {
689 b = p->syslog_logbuffer;
690 } else {
691 b = p->conf.access_logbuffer;
694 if (b->used == 0) {
695 buffer_copy_string_len(b, CONST_STR_LEN(""));
698 for (j = 0; j < p->conf.parsed_format->used; j++) {
699 switch(p->conf.parsed_format->ptr[j]->type) {
700 case FIELD_STRING:
701 buffer_append_string_buffer(b, p->conf.parsed_format->ptr[j]->string);
702 break;
703 case FIELD_FORMAT:
704 switch(p->conf.parsed_format->ptr[j]->field) {
705 case FORMAT_TIMESTAMP:
707 /* cache the generated timestamp */
708 if (srv->cur_ts != *(p->conf.last_generated_accesslog_ts_ptr)) {
709 struct tm tm;
710 #if defined(HAVE_STRUCT_TM_GMTOFF)
711 long scd, hrs, min;
712 #endif
714 buffer_prepare_copy(p->conf.ts_accesslog_str, 255);
715 #if defined(HAVE_STRUCT_TM_GMTOFF)
716 # ifdef HAVE_LOCALTIME_R
717 localtime_r(&(srv->cur_ts), &tm);
718 strftime(p->conf.ts_accesslog_str->ptr, p->conf.ts_accesslog_str->size - 1, p->conf.ts_accesslog_fmt_str->ptr, &tm);
719 # else /* HAVE_LOCALTIME_R */
720 strftime(p->conf.ts_accesslog_str->ptr, p->conf.ts_accesslog_str->size - 1, p->conf.ts_accesslog_fmt_str->ptr, localtime_r(&(srv->cur_ts)));
721 # endif /* HAVE_LOCALTIME_R */
722 p->conf.ts_accesslog_str->used = strlen(p->conf.ts_accesslog_str->ptr) + 1;
724 if (p->conf.append_tz_offset) {
725 buffer_append_string_len(p->conf.ts_accesslog_str, tm.tm_gmtoff >= 0 ? "+" : "-", 1);
727 scd = abs(tm.tm_gmtoff);
728 hrs = scd / 3600;
729 min = (scd % 3600) / 60;
731 /* hours */
732 if (hrs < 10) buffer_append_string_len(p->conf.ts_accesslog_str, CONST_STR_LEN("0"));
733 buffer_append_long(p->conf.ts_accesslog_str, hrs);
735 if (min < 10) buffer_append_string_len(p->conf.ts_accesslog_str, CONST_STR_LEN("0"));
736 buffer_append_long(p->conf.ts_accesslog_str, min);
737 buffer_append_string_len(p->conf.ts_accesslog_str, CONST_STR_LEN("]"));
739 #else /* HAVE_STRUCT_TM_GMTOFF */
740 # ifdef HAVE_GMTIME_R
741 gmtime_r(&(srv->cur_ts), &tm);
742 strftime(p->conf.ts_accesslog_str->ptr, p->conf.ts_accesslog_str->size - 1, p->conf.ts_accesslog_fmt_str->ptr, &tm);
743 # else /* HAVE_GMTIME_R */
744 strftime(p->conf.ts_accesslog_str->ptr, p->conf.ts_accesslog_str->size - 1, p->conf.ts_accesslog_fmt_str->ptr, gmtime(&(srv->cur_ts)));
745 # endif /* HAVE_GMTIME_R */
746 p->conf.ts_accesslog_str->used = strlen(p->conf.ts_accesslog_str->ptr) + 1;
747 #endif /* HAVE_STRUCT_TM_GMTOFF */
749 *(p->conf.last_generated_accesslog_ts_ptr) = srv->cur_ts;
750 newts = 1;
753 buffer_append_string_buffer(b, p->conf.ts_accesslog_str);
755 break;
756 case FORMAT_REMOTE_HOST:
758 /* handle inet_ntop cache */
760 buffer_append_string(b, inet_ntop_cache_get_ip(srv, &(con->dst_addr)));
762 break;
763 case FORMAT_REMOTE_IDENT:
764 /* ident */
765 buffer_append_string_len(b, CONST_STR_LEN("-"));
766 break;
767 case FORMAT_REMOTE_USER:
768 if (NULL != (ds = (data_string *)array_get_element(con->environment, "REMOTE_USER")) && ds->value->used > 1) {
769 accesslog_append_escaped(b, ds->value);
770 } else {
771 buffer_append_string_len(b, CONST_STR_LEN("-"));
773 break;
774 case FORMAT_REQUEST_LINE:
775 if (con->request.request_line->used) {
776 accesslog_append_escaped(b, con->request.request_line);
778 break;
779 case FORMAT_STATUS:
780 buffer_append_long(b, con->http_status);
781 break;
783 case FORMAT_BYTES_OUT_NO_HEADER:
784 if (con->bytes_written > 0) {
785 buffer_append_off_t(b,
786 con->bytes_written - con->bytes_header <= 0 ? 0 : con->bytes_written - con->bytes_header);
787 } else {
788 buffer_append_string_len(b, CONST_STR_LEN("-"));
790 break;
791 case FORMAT_HEADER:
792 if (NULL != (ds = (data_string *)array_get_element(con->request.headers, p->conf.parsed_format->ptr[j]->string->ptr))) {
793 accesslog_append_escaped(b, ds->value);
794 } else {
795 buffer_append_string_len(b, CONST_STR_LEN("-"));
797 break;
798 case FORMAT_RESPONSE_HEADER:
799 if (NULL != (ds = (data_string *)array_get_element(con->response.headers, p->conf.parsed_format->ptr[j]->string->ptr))) {
800 accesslog_append_escaped(b, ds->value);
801 } else {
802 buffer_append_string_len(b, CONST_STR_LEN("-"));
804 break;
805 case FORMAT_ENV:
806 if (NULL != (ds = (data_string *)array_get_element(con->environment, p->conf.parsed_format->ptr[j]->string->ptr))) {
807 accesslog_append_escaped(b, ds->value);
808 } else {
809 buffer_append_string_len(b, CONST_STR_LEN("-"));
811 break;
812 case FORMAT_FILENAME:
813 if (con->physical.path->used > 1) {
814 buffer_append_string_buffer(b, con->physical.path);
815 } else {
816 buffer_append_string_len(b, CONST_STR_LEN("-"));
818 break;
819 case FORMAT_BYTES_OUT:
820 if (con->bytes_written > 0) {
821 buffer_append_off_t(b, con->bytes_written);
822 } else {
823 buffer_append_string_len(b, CONST_STR_LEN("-"));
825 break;
826 case FORMAT_BYTES_IN:
827 if (con->bytes_read > 0) {
828 buffer_append_off_t(b, con->bytes_read);
829 } else {
830 buffer_append_string_len(b, CONST_STR_LEN("-"));
832 break;
833 case FORMAT_TIME_USED:
834 buffer_append_long(b, srv->cur_ts - con->request_start);
835 break;
836 case FORMAT_SERVER_NAME:
837 if (con->server_name->used > 1) {
838 buffer_append_string_buffer(b, con->server_name);
839 } else {
840 buffer_append_string_len(b, CONST_STR_LEN("-"));
842 break;
843 case FORMAT_HTTP_HOST:
844 if (con->uri.authority->used > 1) {
845 accesslog_append_escaped(b, con->uri.authority);
846 } else {
847 buffer_append_string_len(b, CONST_STR_LEN("-"));
849 break;
850 case FORMAT_REQUEST_PROTOCOL:
851 buffer_append_string_len(b,
852 con->request.http_version == HTTP_VERSION_1_1 ? "HTTP/1.1" : "HTTP/1.0", 8);
853 break;
854 case FORMAT_REQUEST_METHOD:
855 buffer_append_string(b, get_http_method_name(con->request.http_method));
856 break;
857 case FORMAT_PERCENT:
858 buffer_append_string_len(b, CONST_STR_LEN("%"));
859 break;
860 case FORMAT_SERVER_PORT:
862 const char *colon;
863 buffer *srvtoken = ((server_socket*)(con->srv_socket))->srv_token;
864 if (srvtoken->ptr[0] == '[') {
865 colon = strstr(srvtoken->ptr, "]:");
866 } else {
867 colon = strchr(srvtoken->ptr, ':');
869 if (colon) {
870 buffer_append_string(b, colon+1);
871 } else {
872 buffer_append_long(b, srv->srvconf.port);
875 break;
876 case FORMAT_QUERY_STRING:
877 accesslog_append_escaped(b, con->uri.query);
878 break;
879 case FORMAT_URL:
880 accesslog_append_escaped(b, con->uri.path_raw);
881 break;
882 case FORMAT_CONNECTION_STATUS:
883 switch(con->keep_alive) {
884 case 0: buffer_append_string_len(b, CONST_STR_LEN("-")); break;
885 default: buffer_append_string_len(b, CONST_STR_LEN("+")); break;
887 break;
888 default:
890 { 'a', FORMAT_REMOTE_ADDR },
891 { 'A', FORMAT_LOCAL_ADDR },
892 { 'C', FORMAT_COOKIE },
893 { 'D', FORMAT_TIME_USED_MS },
896 break;
898 break;
899 default:
900 break;
904 buffer_append_string_len(b, CONST_STR_LEN("\n"));
906 if (p->conf.use_syslog || /* syslog doesn't cache */
907 (p->conf.access_logfile->used && p->conf.access_logfile->ptr[0] == '|') || /* pipes don't cache */
908 newts ||
909 b->used > BUFFER_MAX_REUSE_SIZE) {
910 if (p->conf.use_syslog) {
911 #ifdef HAVE_SYSLOG_H
912 if (b->used > 2) {
913 /* syslog appends a \n on its own */
914 syslog(p->conf.syslog_level, "%*s", (int) b->used - 2, b->ptr);
916 #endif
917 } else if (p->conf.log_access_fd != -1) {
918 write(p->conf.log_access_fd, b->ptr, b->used - 1);
920 buffer_reset(b);
923 return HANDLER_GO_ON;
927 int mod_accesslog_plugin_init(plugin *p);
928 int mod_accesslog_plugin_init(plugin *p) {
929 p->version = LIGHTTPD_VERSION_ID;
930 p->name = buffer_init_string("accesslog");
932 p->init = mod_accesslog_init;
933 p->set_defaults= log_access_open;
934 p->cleanup = mod_accesslog_free;
936 p->handle_request_done = log_access_write;
937 p->handle_sighup = log_access_cycle;
939 p->data = NULL;
941 return 0;