1 #include "first.h" 2 3 #include "sys-time.h" 4 5 #include "base.h" 6 #include "fdevent.h" 7 #include "log.h" 8 #include "buffer.h" 9 #include "http_header.h" 10 #include "sock_addr.h" 11 12 #include "plugin.h" 13 14 #include <sys/types.h> 15 #include <sys/stat.h> 16 17 #include <stdlib.h> 18 #include <string.h> 19 #include <fcntl.h> 20 #include <unistd.h> 21 22 #ifdef HAVE_SYSLOG_H 23 # include <syslog.h> 24 #endif 25 26 typedef struct { 27 char key; 28 enum { 29 FORMAT_UNSET, 30 FORMAT_UNSUPPORTED, 31 FORMAT_PERCENT, 32 FORMAT_REMOTE_HOST, 33 FORMAT_REMOTE_IDENT, 34 FORMAT_REMOTE_USER, 35 FORMAT_TIMESTAMP, 36 FORMAT_REQUEST_LINE, 37 FORMAT_STATUS, 38 FORMAT_BYTES_OUT_NO_HEADER, 39 FORMAT_HEADER, 40 41 FORMAT_REMOTE_ADDR, 42 FORMAT_LOCAL_ADDR, 43 FORMAT_COOKIE, 44 FORMAT_TIME_USED_US, 45 FORMAT_ENV, 46 FORMAT_FILENAME, 47 FORMAT_REQUEST_PROTOCOL, 48 FORMAT_REQUEST_METHOD, 49 FORMAT_SERVER_PORT, 50 FORMAT_QUERY_STRING, 51 FORMAT_TIME_USED, 52 FORMAT_URL, 53 FORMAT_SERVER_NAME, 54 FORMAT_HTTP_HOST, 55 FORMAT_CONNECTION_STATUS, 56 FORMAT_BYTES_IN, 57 FORMAT_BYTES_OUT, 58 59 FORMAT_KEEPALIVE_COUNT, 60 FORMAT_RESPONSE_HEADER, 61 FORMAT_NOTE 62 } type; 63 } format_mapping; 64 65 /** 66 * 67 * 68 * "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" 69 * 70 */ 71 72 static const format_mapping fmap[] = 73 { 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 }, 83 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_US }, 89 { 'e', FORMAT_ENV }, 90 { 'f', FORMAT_FILENAME }, 91 { 'H', FORMAT_REQUEST_PROTOCOL }, 92 { 'k', FORMAT_KEEPALIVE_COUNT }, 93 { 'm', FORMAT_REQUEST_METHOD }, 94 { 'n', FORMAT_NOTE }, 95 { 'p', FORMAT_SERVER_PORT }, 96 { 'P', FORMAT_UNSUPPORTED }, /* we are only one process */ 97 { 'q', FORMAT_QUERY_STRING }, 98 { 'T', FORMAT_TIME_USED }, 99 { 'U', FORMAT_URL }, /* w/o querystring */ 100 { 'v', FORMAT_SERVER_NAME }, 101 { 'V', FORMAT_HTTP_HOST }, 102 { 'X', FORMAT_CONNECTION_STATUS }, 103 { 'I', FORMAT_BYTES_IN }, 104 { 'O', FORMAT_BYTES_OUT }, 105 106 { 'o', FORMAT_RESPONSE_HEADER }, 107 108 { '\0', FORMAT_UNSET } 109 }; 110 111 112 enum e_optflags_time { 113 /* format string is passed to strftime unless other format optflags set 114 * (besides FORMAT_FLAG_TIME_BEGIN or FORMAT_FLAG_TIME_END) */ 115 FORMAT_FLAG_TIME_END = 0x00,/* use request end time (default) */ 116 FORMAT_FLAG_TIME_BEGIN = 0x01,/* use request start time */ 117 FORMAT_FLAG_TIME_SEC = 0x02,/* request time as num sec since epoch */ 118 FORMAT_FLAG_TIME_MSEC = 0x04,/* request time as num msec since epoch */ 119 FORMAT_FLAG_TIME_USEC = 0x08,/* request time as num usec since epoch */ 120 FORMAT_FLAG_TIME_NSEC = 0x10,/* request time as num nsec since epoch */ 121 FORMAT_FLAG_TIME_MSEC_FRAC = 0x20,/* request time msec fraction */ 122 FORMAT_FLAG_TIME_USEC_FRAC = 0x40,/* request time usec fraction */ 123 FORMAT_FLAG_TIME_NSEC_FRAC = 0x80 /* request time nsec fraction */ 124 }; 125 126 enum e_optflags_port { 127 FORMAT_FLAG_PORT_LOCAL = 0x01,/* (default) */ 128 FORMAT_FLAG_PORT_REMOTE = 0x02 129 }; 130 131 132 typedef struct { 133 enum { FIELD_UNSET, FIELD_STRING, FIELD_FORMAT } type; 134 int field; 135 int opt; 136 buffer string; 137 } format_field; 138 139 typedef struct { 140 time_t last_generated_accesslog_ts; 141 buffer ts_accesslog_str; 142 #if defined(__STDC_VERSION__) && __STDC_VERSION__-0 >= 199901L /* C99 */ 143 format_field ptr[]; /* C99 VLA */ 144 #else 145 format_field ptr[1]; 146 #endif 147 } format_fields; 148 149 typedef struct { 150 int log_access_fd; 151 char use_syslog; /* syslog has global buffer */ 152 char piped_logger; 153 unsigned short syslog_level; 154 buffer *access_logbuffer; /* each logfile has a separate buffer */ 155 const buffer *access_logfile; 156 157 format_fields *parsed_format; 158 } plugin_config; 159 160 typedef struct { 161 int log_access_fd; 162 char piped_logger; 163 const buffer *access_logfile; 164 buffer access_logbuffer; /* each logfile has a separate buffer */ 165 } accesslog_st; 166 167 typedef struct { 168 PLUGIN_DATA; 169 plugin_config defaults; 170 plugin_config conf; 171 172 buffer syslog_logbuffer; /* syslog has global buffer. no caching, always written directly */ 173 log_error_st *errh; /* copy of srv->errh */ 174 format_fields *default_format;/* allocated if default format */ 175 } plugin_data; 176 177 INIT_FUNC(mod_accesslog_init) { 178 return calloc(1, sizeof(plugin_data)); 179 } 180 181 static int accesslog_write_all(const int fd, buffer * const b) { 182 const ssize_t wr = (fd >= 0) ? write_all(fd, CONST_BUF_LEN(b)) : 0; 183 buffer_clear(b); /*(clear buffer, even if fd < 0)*/ 184 return (-1 != wr); 185 } 186 187 static void accesslog_append_escaped_str(buffer * const dest, const char * const str, const size_t len) { 188 const char *ptr, *start, *end; 189 190 /* replaces non-printable chars with \xHH where HH is the hex representation of the byte */ 191 /* exceptions: " => \", \ => \\, whitespace chars => \n \t etc. */ 192 if (0 == len) return; 193 buffer_string_prepare_append(dest, len); 194 195 for (ptr = start = str, end = str+len; ptr < end; ++ptr) { 196 unsigned char const c = *(const unsigned char *)ptr; 197 if (c >= ' ' && c <= '~' && c != '"' && c != '\\') { 198 /* nothing to change, add later as one block */ 199 } else { 200 /* copy previous part */ 201 if (start < ptr) { 202 buffer_append_string_len(dest, start, ptr - start); 203 } 204 start = ptr + 1; 205 206 switch (c) { 207 case '"': 208 BUFFER_APPEND_STRING_CONST(dest, "\\\""); 209 break; 210 case '\\': 211 BUFFER_APPEND_STRING_CONST(dest, "\\\\"); 212 break; 213 case '\b': 214 BUFFER_APPEND_STRING_CONST(dest, "\\b"); 215 break; 216 case '\n': 217 BUFFER_APPEND_STRING_CONST(dest, "\\n"); 218 break; 219 case '\r': 220 BUFFER_APPEND_STRING_CONST(dest, "\\r"); 221 break; 222 case '\t': 223 BUFFER_APPEND_STRING_CONST(dest, "\\t"); 224 break; 225 case '\v': 226 BUFFER_APPEND_STRING_CONST(dest, "\\v"); 227 break; 228 default: { 229 /* non printable char => \xHH */ 230 char hh[5] = {'\\','x',0,0,0}; 231 char h = c / 16; 232 hh[2] = (h > 9) ? (h - 10 + 'A') : (h + '0'); 233 h = c % 16; 234 hh[3] = (h > 9) ? (h - 10 + 'A') : (h + '0'); 235 buffer_append_string_len(dest, &hh[0], 4); 236 } 237 break; 238 } 239 } 240 } 241 242 if (start < end) { 243 buffer_append_string_len(dest, start, end - start); 244 } 245 } 246 247 static void accesslog_append_escaped(buffer *dest, const buffer *str) { 248 accesslog_append_escaped_str(dest, CONST_BUF_LEN(str)); 249 } 250 251 __attribute_cold__ 252 static format_fields * accesslog_parse_format_err(log_error_st *errh, const char *file, unsigned int line, format_field *f, const char *msg) { 253 log_error(errh, file, line, "%s", msg); 254 for (; f->type != FIELD_UNSET; ++f) free(f->string.ptr); 255 return NULL; 256 } 257 258 static format_fields * accesslog_parse_format(const char * const format, const size_t flen, log_error_st * const errh) { 259 /* common log format (the default) results in 18 elements, 260 * so 127 should be enough except for obscene custom usage */ 261 size_t i, j, k = 0, start = 0; 262 uint32_t used = 0; 263 const uint32_t sz = 127;/* (sz+1 must match fptr[] num elts below) */ 264 format_field *f; 265 format_field fptr[128]; /* (128 elements takes 4k on stack in 64-bit) */ 266 memset(fptr, 0, sizeof(fptr)); 267 if (0 != FIELD_UNSET) return NULL; 268 269 if (0 == flen) return NULL; 270 271 for (i = 0; i < flen; ++i) { 272 if (format[i] != '%') continue; 273 274 if (i > 0 && start != i) { 275 /* copy the string before this % */ 276 if (used == sz) 277 return accesslog_parse_format_err(errh, __FILE__, __LINE__, fptr, 278 "too many fields (>= 127) in accesslog.format"); 279 280 f = fptr+used; 281 f->type = FIELD_STRING; 282 memset(&f->string, 0, sizeof(buffer)); 283 buffer_copy_string_len(&f->string, format + start, i - start); 284 285 ++used; 286 } 287 288 /* we need a new field */ 289 if (used == sz) 290 return accesslog_parse_format_err(errh, __FILE__, __LINE__, fptr, 291 "too many fields (>= 127) in accesslog.format"); 292 293 /* search for the terminating command */ 294 switch (format[i+1]) { 295 case '>': 296 case '<': 297 /* after the } has to be a character */ 298 if (format[i+2] == '\0') { 299 return accesslog_parse_format_err(errh, __FILE__, __LINE__, fptr, 300 "%< and %> have to be followed by a format-specifier"); 301 } 302 303 304 for (j = 0; fmap[j].key != '\0'; j++) { 305 if (fmap[j].key != format[i+2]) continue; 306 307 /* found key */ 308 309 f = fptr+used; 310 f->type = FIELD_FORMAT; 311 f->field = fmap[j].type; 312 f->opt = 0; 313 f->string.ptr = NULL; 314 315 ++used; 316 317 break; 318 } 319 320 if (fmap[j].key == '\0') { 321 return accesslog_parse_format_err(errh, __FILE__, __LINE__, fptr, 322 "%< and %> have to be followed by a valid format-specifier"); 323 } 324 325 start = i + 3; 326 i = start - 1; /* skip the string */ 327 328 break; 329 case '{': 330 /* go forward to } */ 331 332 for (k = i+2; k < flen; ++k) { 333 if (format[k] == '}') break; 334 } 335 336 if (k == flen) { 337 return accesslog_parse_format_err(errh, __FILE__, __LINE__, fptr, 338 "%{ has to be terminated by a }"); 339 } 340 341 /* after the } has to be a character */ 342 if (format[k+1] == '\0') { 343 return accesslog_parse_format_err(errh, __FILE__, __LINE__, fptr, 344 "%{...} has to be followed by a format-specifier"); 345 } 346 347 if (k == i + 2) { 348 return accesslog_parse_format_err(errh, __FILE__, __LINE__, fptr, 349 "%{...} has to contain a string"); 350 } 351 352 for (j = 0; fmap[j].key != '\0'; j++) { 353 if (fmap[j].key != format[k+1]) continue; 354 355 /* found key */ 356 357 f = fptr+used; 358 f->type = FIELD_FORMAT; 359 f->field = fmap[j].type; 360 f->opt = 0; 361 memset(&f->string, 0, sizeof(buffer)); 362 buffer_copy_string_len(&f->string, format + i + 2, k - (i + 2)); 363 364 ++used; 365 366 break; 367 } 368 369 if (fmap[j].key == '\0') { 370 return accesslog_parse_format_err(errh, __FILE__, __LINE__, fptr, 371 "%{...} has to be followed by a valid format-specifier"); 372 } 373 374 start = k + 2; 375 i = start - 1; /* skip the string */ 376 377 break; 378 default: 379 /* after the % has to be a character */ 380 if (format[i+1] == '\0') { 381 return accesslog_parse_format_err(errh, __FILE__, __LINE__, fptr, 382 "% has to be followed by a format-specifier"); 383 } 384 385 for (j = 0; fmap[j].key != '\0'; j++) { 386 if (fmap[j].key != format[i+1]) continue; 387 388 /* found key */ 389 390 f = fptr+used; 391 f->type = FIELD_FORMAT; 392 f->field = fmap[j].type; 393 f->string.ptr = NULL; 394 f->opt = 0; 395 396 ++used; 397 398 break; 399 } 400 401 if (fmap[j].key == '\0') { 402 return accesslog_parse_format_err(errh, __FILE__, __LINE__, fptr, 403 "% has to be followed by a valid format-specifier"); 404 } 405 406 start = i + 2; 407 i = start - 1; /* skip the string */ 408 409 break; 410 } 411 } 412 413 if (start < i) { 414 /* copy the string */ 415 if (used == sz) 416 return accesslog_parse_format_err(errh, __FILE__, __LINE__, fptr, 417 "too many fields (>= 127) in accesslog.format"); 418 419 f = fptr+used; 420 f->type = FIELD_STRING; 421 memset(&f->string, 0, sizeof(buffer)); 422 buffer_copy_string_len(&f->string, format + start, i - start); 423 424 ++used; 425 } 426 427 format_fields * const fields = 428 malloc(sizeof(format_fields) + ((used+1) * sizeof(format_field))); 429 force_assert(fields); 430 memset(fields, 0, sizeof(format_fields)); 431 memcpy(fields->ptr, fptr, (used+1) * sizeof(format_field)); 432 return fields; 433 } 434 435 static void mod_accesslog_free_accesslog(accesslog_st * const x, plugin_data *p) { 436 /*(piped loggers are closed in fdevent_close_logger_pipes())*/ 437 if (!x->piped_logger && -1 != x->log_access_fd) { 438 if (!accesslog_write_all(x->log_access_fd, &x->access_logbuffer)) { 439 log_perror(p->errh, __FILE__, __LINE__, 440 "writing access log entry failed: %s", x->access_logfile->ptr); 441 } 442 close(x->log_access_fd); 443 } 444 free(x->access_logbuffer.ptr); 445 free(x); 446 } 447 448 static void mod_accesslog_free_format_fields(format_fields * const ff) { 449 for (format_field *f = ff->ptr; f->type != FIELD_UNSET; ++f) 450 free(f->string.ptr); 451 free(ff->ts_accesslog_str.ptr); 452 free(ff); 453 } 454 455 FREE_FUNC(mod_accesslog_free) { 456 plugin_data * const p = p_d; 457 free(p->syslog_logbuffer.ptr); 458 if (NULL == p->cvlist) return; 459 /* (init i to 0 if global context; to 1 to skip empty global context) */ 460 for (int i = !p->cvlist[0].v.u2[1], used = p->nconfig; i < used; ++i) { 461 config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0]; 462 for (; -1 != cpv->k_id; ++cpv) { 463 if (cpv->vtype != T_CONFIG_LOCAL || NULL == cpv->v.v) continue; 464 switch (cpv->k_id) { 465 case 0: /* accesslog.filename */ 466 mod_accesslog_free_accesslog(cpv->v.v, p); 467 break; 468 case 1: /* accesslog.format */ 469 mod_accesslog_free_format_fields(cpv->v.v); 470 break; 471 default: 472 break; 473 } 474 } 475 } 476 477 if (NULL != p->default_format) { 478 mod_accesslog_free_format_fields(p->default_format); 479 } 480 } 481 482 static void mod_accesslog_merge_config_cpv(plugin_config * const pconf, const config_plugin_value_t * const cpv) { 483 switch (cpv->k_id) { /* index into static config_plugin_keys_t cpk[] */ 484 case 0:{/* accesslog.filename */ 485 if (cpv->vtype != T_CONFIG_LOCAL) break; 486 accesslog_st * const x = cpv->v.v; 487 pconf->log_access_fd = x->log_access_fd; 488 pconf->piped_logger = x->piped_logger; 489 pconf->access_logfile = x->access_logfile; 490 pconf->access_logbuffer = &x->access_logbuffer; 491 break; 492 } 493 case 1:{/* accesslog.format */ 494 if (cpv->vtype != T_CONFIG_LOCAL) break; 495 pconf->parsed_format = cpv->v.v; 496 break; 497 } 498 case 2: /* accesslog.use-syslog */ 499 pconf->use_syslog = (int)cpv->v.u; 500 break; 501 case 3: /* accesslog.syslog-level */ 502 pconf->syslog_level = cpv->v.shrt; 503 break; 504 default:/* should not happen */ 505 return; 506 } 507 } 508 509 static void mod_accesslog_merge_config(plugin_config * const pconf, const config_plugin_value_t *cpv) { 510 do { 511 mod_accesslog_merge_config_cpv(pconf, cpv); 512 } while ((++cpv)->k_id != -1); 513 } 514 515 static void mod_accesslog_patch_config(request_st * const r, plugin_data * const p) { 516 memcpy(&p->conf, &p->defaults, sizeof(plugin_config)); 517 for (int i = 1, used = p->nconfig; i < used; ++i) { 518 if (config_check_cond(r, (uint32_t)p->cvlist[i].k_id)) 519 mod_accesslog_merge_config(&p->conf, p->cvlist + p->cvlist[i].v.u2[0]); 520 } 521 } 522 523 static format_fields * mod_accesslog_process_format(const char * const format, const size_t flen, server * const srv); 524 525 SETDEFAULTS_FUNC(mod_accesslog_set_defaults) { 526 static const config_plugin_keys_t cpk[] = { 527 { CONST_STR_LEN("accesslog.filename"), 528 T_CONFIG_STRING, 529 T_CONFIG_SCOPE_CONNECTION } 530 ,{ CONST_STR_LEN("accesslog.format"), 531 T_CONFIG_STRING, 532 T_CONFIG_SCOPE_CONNECTION } 533 ,{ CONST_STR_LEN("accesslog.use-syslog"), 534 T_CONFIG_BOOL, 535 T_CONFIG_SCOPE_CONNECTION } 536 ,{ CONST_STR_LEN("accesslog.syslog-level"), 537 T_CONFIG_SHORT, 538 T_CONFIG_SCOPE_CONNECTION } 539 ,{ NULL, 0, 540 T_CONFIG_UNSET, 541 T_CONFIG_SCOPE_UNSET } 542 }; 543 544 plugin_data * const p = p_d; 545 p->errh = srv->errh; 546 if (!config_plugin_values_init(srv, p, cpk, "mod_accesslog")) 547 return HANDLER_ERROR; 548 549 /* process and validate config directives 550 * (init i to 0 if global context; to 1 to skip empty global context) */ 551 for (int i = !p->cvlist[0].v.u2[1]; i < p->nconfig; ++i) { 552 config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0]; 553 int use_syslog = 0; 554 accesslog_st *x = NULL; 555 for (; -1 != cpv->k_id; ++cpv) { 556 switch (cpv->k_id) { 557 case 0: /* accesslog.filename */ 558 x = calloc(1, sizeof(accesslog_st)); 559 force_assert(x); 560 x->log_access_fd = -1; 561 x->piped_logger = (cpv->v.b->ptr[0] == '|'); 562 x->access_logfile = cpv->v.b; 563 cpv->vtype = T_CONFIG_LOCAL; 564 cpv->v.v = x; 565 break; 566 case 1: /* accesslog.format */ 567 if (NULL != strchr(cpv->v.b->ptr, '\\')) { 568 /* process basic backslash-escapes in format string */ 569 buffer *b; 570 *(const buffer **)&b = cpv->v.b; 571 char *t = b->ptr; 572 for (char *s = t; *s; ++s) { 573 if (s[0] != '\\') { *t++ = *s; continue; } 574 if (s[1] == '\0') continue; /*(ignore dangling '\\')*/ 575 switch (*++s) { 576 case 'a': *t++ = '\a'; break; /* bell */ 577 case 'b': *t++ = '\b'; break; /* backspace */ 578 case 'f': *t++ = '\f'; break; /* form feed */ 579 case 'n': *t++ = '\n'; break; /* newline */ 580 case 'r': *t++ = '\r'; break; /* carriage return */ 581 case 't': *t++ = '\t'; break; /* horizontal tab */ 582 case 'v': *t++ = '\v'; break; /* vertical tab */ 583 /*case '"':*/ 584 /*case '\\':*/ 585 default: *t++ = *s; break; /*(use literal char)*/ 586 } 587 } 588 buffer_string_set_length(b, (size_t)(t - b->ptr)); 589 } 590 cpv->v.v = 591 mod_accesslog_process_format(CONST_BUF_LEN(cpv->v.b), srv); 592 if (NULL == cpv->v.v) return HANDLER_ERROR; 593 cpv->vtype = T_CONFIG_LOCAL; 594 break; 595 case 2: /* accesslog.use-syslog */ 596 use_syslog = (int)cpv->v.u; 597 break; 598 case 3: /* accesslog.syslog-level */ 599 break; 600 default:/* should not happen */ 601 break; 602 } 603 } 604 605 if (srv->srvconf.preflight_check) continue; 606 607 if (use_syslog) continue; /* ignore the next checks */ 608 if (NULL == x || buffer_string_is_empty(x->access_logfile)) continue; 609 610 x->log_access_fd = fdevent_open_logger(x->access_logfile->ptr); 611 if (-1 == x->log_access_fd) { 612 log_perror(srv->errh, __FILE__, __LINE__, 613 "opening log '%s' failed", x->access_logfile->ptr); 614 return HANDLER_ERROR; 615 } 616 } 617 618 p->defaults.log_access_fd = -1; 619 p->defaults.syslog_level = LOG_INFO; 620 621 /* initialize p->defaults from global config context */ 622 if (p->nconfig > 0 && p->cvlist->v.u2[1]) { 623 const config_plugin_value_t *cpv = p->cvlist + p->cvlist->v.u2[0]; 624 if (-1 != cpv->k_id) 625 mod_accesslog_merge_config(&p->defaults, cpv); 626 } 627 628 if (NULL == p->defaults.parsed_format) { 629 /* (set default format even if p->use_syslog since 630 * some other condition might enable logfile) */ 631 static const char fmt[] = 632 "%h %V %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\""; 633 p->defaults.parsed_format = p->default_format = 634 mod_accesslog_process_format(CONST_STR_LEN(fmt), srv); 635 if (NULL == p->default_format) return HANDLER_ERROR; 636 } 637 638 return HANDLER_GO_ON; 639 } 640 641 static format_fields * mod_accesslog_process_format(const char * const format, const size_t flen, server * const srv) { 642 format_fields * const parsed_format = 643 accesslog_parse_format(format, flen, srv->errh); 644 if (NULL == parsed_format) { 645 log_error(srv->errh, __FILE__, __LINE__, 646 "parsing accesslog-definition failed: %s", format); 647 return NULL; 648 } 649 650 uint32_t tcount = 0; 651 for (format_field *f = parsed_format->ptr; f->type != FIELD_UNSET; ++f) { 652 const buffer * const fstr = &f->string; 653 if (FIELD_FORMAT != f->type) continue; 654 if (FORMAT_TIMESTAMP == f->field) { 655 if (!buffer_string_is_empty(fstr)) { 656 const char *ptr = fstr->ptr; 657 if (0 == strncmp(ptr, "begin:", sizeof("begin:")-1)) { 658 f->opt |= FORMAT_FLAG_TIME_BEGIN; 659 ptr += sizeof("begin:")-1; 660 } else if (0 == strncmp(ptr, "end:", sizeof("end:")-1)) { 661 f->opt |= FORMAT_FLAG_TIME_END; 662 ptr += sizeof("end:")-1; 663 } 664 if (0 == strcmp(ptr, "sec")) f->opt |= FORMAT_FLAG_TIME_SEC; 665 else if (0 == strcmp(ptr, "msec")) f->opt |= FORMAT_FLAG_TIME_MSEC; 666 else if (0 == strcmp(ptr, "usec")) f->opt |= FORMAT_FLAG_TIME_USEC; 667 else if (0 == strcmp(ptr, "nsec")) f->opt |= FORMAT_FLAG_TIME_NSEC; 668 else if (0 == strcmp(ptr, "msec_frac")) f->opt |= FORMAT_FLAG_TIME_MSEC_FRAC; 669 else if (0 == strcmp(ptr, "usec_frac")) f->opt |= FORMAT_FLAG_TIME_USEC_FRAC; 670 else if (0 == strcmp(ptr, "nsec_frac")) f->opt |= FORMAT_FLAG_TIME_NSEC_FRAC; 671 else if (NULL == strchr(ptr, '%')) { 672 log_error(srv->errh, __FILE__, __LINE__, 673 "constant string for time format (misspelled token? or missing '%%'): %s", format); 674 mod_accesslog_free_format_fields(parsed_format); 675 return NULL; 676 } 677 } 678 679 /* make sure they didn't try to send the timestamp in twice 680 * (would invalidate pconf->parsed_format.ts_accesslog_str cache of timestamp str) */ 681 if (!(f->opt & ~(FORMAT_FLAG_TIME_BEGIN|FORMAT_FLAG_TIME_END|FORMAT_FLAG_TIME_SEC)) && ++tcount > 1) { 682 log_error(srv->errh, __FILE__, __LINE__, 683 "you may not use strftime timestamp format %%{}t twice in the same access log: %s", format); 684 mod_accesslog_free_format_fields(parsed_format); 685 return NULL; 686 } 687 688 if (f->opt & FORMAT_FLAG_TIME_BEGIN) srv->srvconf.high_precision_timestamps = 1; 689 } else if (FORMAT_TIME_USED_US == f->field) { 690 f->opt |= FORMAT_FLAG_TIME_USEC; 691 srv->srvconf.high_precision_timestamps = 1; 692 } else if (FORMAT_TIME_USED == f->field) { 693 if (buffer_string_is_empty(fstr) 694 || buffer_is_equal_string(fstr, CONST_STR_LEN("s")) 695 || buffer_is_equal_string(fstr, CONST_STR_LEN("sec"))) f->opt |= FORMAT_FLAG_TIME_SEC; 696 else if (buffer_is_equal_string(fstr, CONST_STR_LEN("ms")) 697 || buffer_is_equal_string(fstr, CONST_STR_LEN("msec"))) f->opt |= FORMAT_FLAG_TIME_MSEC; 698 else if (buffer_is_equal_string(fstr, CONST_STR_LEN("us")) 699 || buffer_is_equal_string(fstr, CONST_STR_LEN("usec"))) f->opt |= FORMAT_FLAG_TIME_USEC; 700 else if (buffer_is_equal_string(fstr, CONST_STR_LEN("ns")) 701 || buffer_is_equal_string(fstr, CONST_STR_LEN("nsec"))) f->opt |= FORMAT_FLAG_TIME_NSEC; 702 else { 703 log_error(srv->errh, __FILE__, __LINE__, 704 "invalid time unit in %%{UNIT}T: %s", format); 705 mod_accesslog_free_format_fields(parsed_format); 706 return NULL; 707 } 708 709 if (f->opt & ~(FORMAT_FLAG_TIME_SEC)) srv->srvconf.high_precision_timestamps = 1; 710 } else if (FORMAT_COOKIE == f->field) { 711 if (buffer_string_is_empty(fstr)) f->type = FIELD_STRING; /*(blank)*/ 712 } else if (FORMAT_SERVER_PORT == f->field) { 713 if (buffer_string_is_empty(fstr)) 714 f->opt |= FORMAT_FLAG_PORT_LOCAL; 715 else if (buffer_is_equal_string(fstr, CONST_STR_LEN("canonical"))) 716 f->opt |= FORMAT_FLAG_PORT_LOCAL; 717 else if (buffer_is_equal_string(fstr, CONST_STR_LEN("local"))) 718 f->opt |= FORMAT_FLAG_PORT_LOCAL; 719 else if (buffer_is_equal_string(fstr, CONST_STR_LEN("remote"))) 720 f->opt |= FORMAT_FLAG_PORT_REMOTE; 721 else { 722 log_error(srv->errh, __FILE__, __LINE__, 723 "invalid format %%{canonical,local,remote}p: %s", format); 724 mod_accesslog_free_format_fields(parsed_format); 725 return NULL; 726 } 727 } else if (FORMAT_HEADER == f->field 728 || FORMAT_RESPONSE_HEADER == f->field) { 729 f->opt = http_header_hkey_get(CONST_BUF_LEN(fstr)); 730 } 731 } 732 733 return parsed_format; 734 } 735 736 static void log_access_flush(plugin_data * const p) { 737 /* future: might be slightly faster to have allocated array of open files 738 * rather than walking config, but only might matter with many directives */ 739 /* (init i to 0 if global context; to 1 to skip empty global context) */ 740 for (int i = !p->cvlist[0].v.u2[1]; i < p->nconfig; ++i) { 741 config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0]; 742 for (; -1 != cpv->k_id; ++cpv) { 743 switch (cpv->k_id) { 744 case 0: /* accesslog.filename */ 745 if (cpv->vtype == T_CONFIG_LOCAL && NULL != cpv->v.v) { 746 accesslog_st * const x = cpv->v.v; 747 if (buffer_string_is_empty(&x->access_logbuffer)) continue; 748 if (!accesslog_write_all(x->log_access_fd, 749 &x->access_logbuffer)) { 750 log_perror(p->errh, __FILE__, __LINE__, 751 "writing access log entry failed: %s", 752 x->access_logfile->ptr); 753 } 754 } 755 break; 756 default: 757 break; 758 } 759 } 760 } 761 } 762 763 TRIGGER_FUNC(log_access_periodic_flush) { 764 /* flush buffered access logs every 4 seconds */ 765 if (0 == (log_monotonic_secs & 3)) log_access_flush((plugin_data *)p_d); 766 UNUSED(srv); 767 return HANDLER_GO_ON; 768 } 769 770 SIGHUP_FUNC(log_access_cycle) { 771 plugin_data * const p = p_d; 772 773 log_access_flush(p); 774 775 /* future: might be slightly faster to have allocated array of open files 776 * rather than walking config, but only might matter with many directives */ 777 /* (init i to 0 if global context; to 1 to skip empty global context) */ 778 for (int i = !p->cvlist[0].v.u2[1]; i < p->nconfig; ++i) { 779 config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0]; 780 for (; -1 != cpv->k_id; ++cpv) { 781 if (cpv->vtype != T_CONFIG_LOCAL || NULL == cpv->v.v) continue; 782 switch (cpv->k_id) { 783 case 0:{/* accesslog.filename */ 784 accesslog_st * const x = cpv->v.v; 785 if (x->piped_logger) continue; 786 if (buffer_string_is_empty(x->access_logfile)) continue; 787 if (-1 == fdevent_cycle_logger(x->access_logfile->ptr, 788 &x->log_access_fd)) { 789 log_perror(srv->errh, __FILE__, __LINE__, 790 "cycling access log failed: %s", x->access_logfile->ptr); 791 } 792 /*(update p->defaults after cycling log)*/ 793 if (0 == i) p->defaults.log_access_fd = x->log_access_fd; 794 break; 795 } 796 default: 797 break; 798 } 799 } 800 } 801 802 return HANDLER_GO_ON; 803 } 804 805 static int log_access_record (const request_st * const r, buffer * const b, format_fields * const parsed_format) { 806 const connection * const con = r->con; 807 const buffer *vb; 808 struct timespec ts = { 0, 0 }; 809 int flush = 0; 810 811 for (const format_field *f = parsed_format->ptr; f->type != FIELD_UNSET; ++f) { 812 switch(f->type) { 813 case FIELD_STRING: 814 buffer_append_string_buffer(b, &f->string); 815 break; 816 case FIELD_FORMAT: 817 switch(f->field) { 818 case FORMAT_TIMESTAMP: 819 820 if (f->opt & ~(FORMAT_FLAG_TIME_BEGIN|FORMAT_FLAG_TIME_END)) { 821 if (f->opt & FORMAT_FLAG_TIME_SEC) { 822 time_t t = (!(f->opt & FORMAT_FLAG_TIME_BEGIN)) ? log_epoch_secs : r->start_hp.tv_sec; 823 buffer_append_int(b, (intmax_t)t); 824 } else if (f->opt & (FORMAT_FLAG_TIME_MSEC|FORMAT_FLAG_TIME_USEC|FORMAT_FLAG_TIME_NSEC)) { 825 off_t t; /*(expected to be 64-bit since large file support enabled)*/ 826 long ns; 827 if (!(f->opt & FORMAT_FLAG_TIME_BEGIN)) { 828 if (0 == ts.tv_sec) log_clock_gettime_realtime(&ts); 829 t = (off_t)ts.tv_sec; 830 ns = ts.tv_nsec; 831 } else { 832 t = (off_t)r->start_hp.tv_sec; 833 ns = r->start_hp.tv_nsec; 834 } 835 if (f->opt & FORMAT_FLAG_TIME_MSEC) { 836 t *= 1000; 837 t += (ns + 999999) / 1000000; /* ceil */ 838 } else if (f->opt & FORMAT_FLAG_TIME_USEC) { 839 t *= 1000000; 840 t += (ns + 999) / 1000; /* ceil */ 841 } else {/*(f->opt & FORMAT_FLAG_TIME_NSEC)*/ 842 t *= 1000000000; 843 t += ns; 844 } 845 buffer_append_int(b, (intmax_t)t); 846 } else { /*(FORMAT_FLAG_TIME_MSEC_FRAC|FORMAT_FLAG_TIME_USEC_FRAC|FORMAT_FLAG_TIME_NSEC_FRAC)*/ 847 long ns; 848 char *ptr; 849 if (!(f->opt & FORMAT_FLAG_TIME_BEGIN)) { 850 if (0 == ts.tv_sec) log_clock_gettime_realtime(&ts); 851 ns = ts.tv_nsec; 852 } else { 853 ns = r->start_hp.tv_nsec; 854 } 855 /*assert(t < 1000000000);*/ 856 if (f->opt & FORMAT_FLAG_TIME_MSEC_FRAC) { 857 ns += 999999; /* ceil */ 858 ns /= 1000000; 859 buffer_append_string_len(b, CONST_STR_LEN("000")); 860 } else if (f->opt & FORMAT_FLAG_TIME_USEC_FRAC) { 861 ns += 999; /* ceil */ 862 ns /= 1000; 863 buffer_append_string_len(b, CONST_STR_LEN("000000")); 864 } else {/*(f->opt & FORMAT_FLAG_TIME_NSEC_FRAC)*/ 865 buffer_append_string_len(b, CONST_STR_LEN("000000000")); 866 } 867 for (ptr = b->ptr + buffer_string_length(b); ns > 0; ns /= 10) 868 *--ptr = (ns % 10) + '0'; 869 } 870 } else { 871 buffer * const ts_accesslog_str = &parsed_format->ts_accesslog_str; 872 /* cache the generated timestamp (only if ! FORMAT_FLAG_TIME_BEGIN) */ 873 time_t t; 874 struct tm tm; 875 876 if (!(f->opt & FORMAT_FLAG_TIME_BEGIN)) { 877 const time_t cur_ts = log_epoch_secs; 878 if (parsed_format->last_generated_accesslog_ts == cur_ts) { 879 buffer_append_string_buffer(b, ts_accesslog_str); 880 break; 881 } 882 t = parsed_format->last_generated_accesslog_ts = cur_ts; 883 flush = 1; 884 } else { 885 t = r->start_hp.tv_sec; 886 } 887 888 const char *fmt = buffer_string_is_empty(&f->string) 889 ? NULL 890 : f->string.ptr; 891 buffer_clear(ts_accesslog_str); 892 #if defined(HAVE_STRUCT_TM_GMTOFF) 893 buffer_append_strftime(ts_accesslog_str, 894 fmt ? fmt : "[%d/%b/%Y:%T %z]", 895 localtime_r(&t, &tm)); 896 #else /* HAVE_STRUCT_TM_GMTOFF */ 897 buffer_append_strftime(ts_accesslog_str, 898 fmt ? fmt : "[%d/%b/%Y:%T +0000]", 899 gmtime_r(&t, &tm)); 900 #endif /* HAVE_STRUCT_TM_GMTOFF */ 901 buffer_append_string_buffer(b, ts_accesslog_str); 902 } 903 break; 904 case FORMAT_TIME_USED: 905 case FORMAT_TIME_USED_US: 906 if (f->opt & FORMAT_FLAG_TIME_SEC) { 907 buffer_append_int(b, log_epoch_secs - r->start_hp.tv_sec); 908 } else { 909 const struct timespec * const bs = &r->start_hp; 910 off_t tdiff; /*(expected to be 64-bit since large file support enabled)*/ 911 if (0 == ts.tv_sec) log_clock_gettime_realtime(&ts); 912 tdiff = (off_t)(ts.tv_sec - bs->tv_sec)*1000000000 + (ts.tv_nsec - bs->tv_nsec); 913 if (tdiff <= 0) { 914 /* sanity check for time moving backwards 915 * (daylight savings adjustment or leap seconds or ?) */ 916 tdiff = -1; 917 } else if (f->opt & FORMAT_FLAG_TIME_MSEC) { 918 tdiff += 999999; /* ceil */ 919 tdiff /= 1000000; 920 } else if (f->opt & FORMAT_FLAG_TIME_USEC) { 921 tdiff += 999; /* ceil */ 922 tdiff /= 1000; 923 } /* else (f->opt & FORMAT_FLAG_TIME_NSEC) */ 924 buffer_append_int(b, (intmax_t)tdiff); 925 } 926 break; 927 case FORMAT_REMOTE_ADDR: 928 case FORMAT_REMOTE_HOST: 929 buffer_append_string_buffer(b, con->dst_addr_buf); 930 break; 931 case FORMAT_REMOTE_IDENT: 932 /* ident */ 933 buffer_append_string_len(b, CONST_STR_LEN("-")); 934 break; 935 case FORMAT_REMOTE_USER: 936 if (NULL != (vb = http_header_env_get(r, CONST_STR_LEN("REMOTE_USER")))) { 937 accesslog_append_escaped(b, vb); 938 } else { 939 buffer_append_string_len(b, CONST_STR_LEN("-")); 940 } 941 break; 942 case FORMAT_REQUEST_LINE: 943 /*(attempt to reconstruct request line)*/ 944 http_method_append(b, r->http_method); 945 buffer_append_string_len(b, CONST_STR_LEN(" ")); 946 accesslog_append_escaped(b, &r->target_orig); 947 buffer_append_string_len(b, CONST_STR_LEN(" ")); 948 http_version_append(b, r->http_version); 949 break; 950 case FORMAT_STATUS: 951 buffer_append_int(b, r->http_status); 952 break; 953 954 case FORMAT_BYTES_OUT_NO_HEADER: 955 { 956 off_t bytes = r->http_version <= HTTP_VERSION_1_1 957 ? con->bytes_written - r->bytes_written_ckpt 958 : r->write_queue.bytes_out; 959 if (bytes > 0) { 960 bytes -= (off_t)r->resp_header_len; 961 buffer_append_int(b, bytes > 0 ? bytes : 0); 962 } else { 963 buffer_append_string_len(b, CONST_STR_LEN("-")); 964 } 965 break; 966 } 967 case FORMAT_HEADER: 968 if (NULL != (vb = http_header_request_get(r, f->opt, CONST_BUF_LEN(&f->string)))) { 969 accesslog_append_escaped(b, vb); 970 } else { 971 buffer_append_string_len(b, CONST_STR_LEN("-")); 972 } 973 break; 974 case FORMAT_RESPONSE_HEADER: 975 if (NULL != (vb = http_header_response_get(r, f->opt, CONST_BUF_LEN(&f->string)))) { 976 accesslog_append_escaped(b, vb); 977 } else { 978 buffer_append_string_len(b, CONST_STR_LEN("-")); 979 } 980 break; 981 case FORMAT_ENV: 982 case FORMAT_NOTE: 983 if (NULL != (vb = http_header_env_get(r, CONST_BUF_LEN(&f->string)))) { 984 accesslog_append_escaped(b, vb); 985 } else { 986 buffer_append_string_len(b, CONST_STR_LEN("-")); 987 } 988 break; 989 case FORMAT_FILENAME: 990 if (!buffer_string_is_empty(&r->physical.path)) { 991 buffer_append_string_buffer(b, &r->physical.path); 992 } else { 993 buffer_append_string_len(b, CONST_STR_LEN("-")); 994 } 995 break; 996 case FORMAT_BYTES_OUT: 997 { 998 off_t bytes = r->http_version <= HTTP_VERSION_1_1 999 ? con->bytes_written - r->bytes_written_ckpt 1000 : r->write_queue.bytes_out; 1001 if (bytes > 0) { 1002 buffer_append_int(b, bytes); 1003 } else { 1004 buffer_append_string_len(b, CONST_STR_LEN("-")); 1005 } 1006 break; 1007 } 1008 case FORMAT_BYTES_IN: 1009 { 1010 off_t bytes = r->http_version <= HTTP_VERSION_1_1 1011 ? con->bytes_read - r->bytes_read_ckpt 1012 : r->read_queue.bytes_in + (off_t)r->rqst_header_len; 1013 if (bytes > 0) { 1014 buffer_append_int(b, bytes); 1015 } else { 1016 buffer_append_string_len(b, CONST_STR_LEN("-")); 1017 } 1018 break; 1019 } 1020 case FORMAT_SERVER_NAME: 1021 if (!buffer_string_is_empty(r->server_name)) { 1022 buffer_append_string_buffer(b, r->server_name); 1023 } else { 1024 buffer_append_string_len(b, CONST_STR_LEN("-")); 1025 } 1026 break; 1027 case FORMAT_HTTP_HOST: 1028 if (!buffer_string_is_empty(&r->uri.authority)) { 1029 accesslog_append_escaped(b, &r->uri.authority); 1030 } else { 1031 buffer_append_string_len(b, CONST_STR_LEN("-")); 1032 } 1033 break; 1034 case FORMAT_REQUEST_PROTOCOL: 1035 http_version_append(b, r->http_version); 1036 break; 1037 case FORMAT_REQUEST_METHOD: 1038 http_method_append(b, r->http_method); 1039 break; 1040 case FORMAT_PERCENT: 1041 buffer_append_string_len(b, CONST_STR_LEN("%")); 1042 break; 1043 case FORMAT_LOCAL_ADDR: 1044 { 1045 /* (perf: not using getsockname() and 1046 * sock_addr_cache_inet_ntop_copy_buffer()) 1047 * (still useful if admin has configured explicit listen IPs) */ 1048 const server_socket * const srv_sock = con->srv_socket; 1049 buffer_append_string_len(b, srv_sock->srv_token->ptr, 1050 srv_sock->srv_token_colon); 1051 } 1052 break; 1053 case FORMAT_SERVER_PORT: 1054 if (f->opt & FORMAT_FLAG_PORT_REMOTE) { 1055 buffer_append_int(b, sock_addr_get_port(&con->dst_addr)); 1056 } else { /* if (f->opt & FORMAT_FLAG_PORT_LOCAL) *//*(default)*/ 1057 const server_socket * const srv_sock = con->srv_socket; 1058 const buffer * const srv_token = srv_sock->srv_token; 1059 const size_t tlen = buffer_string_length(srv_token); 1060 size_t colon = srv_sock->srv_token_colon; 1061 if (colon < tlen) /*(colon != tlen)*/ 1062 buffer_append_string_len(b, srv_token->ptr+colon+1, 1063 tlen - (colon+1)); 1064 } 1065 break; 1066 case FORMAT_QUERY_STRING: 1067 accesslog_append_escaped(b, &r->uri.query); 1068 break; 1069 case FORMAT_URL: 1070 { 1071 const uint32_t len = buffer_string_length(&r->target); 1072 const char * const qmark = memchr(r->target.ptr, '?', len); 1073 accesslog_append_escaped_str(b, r->target.ptr, qmark ? (uint32_t)(qmark - r->target.ptr) : len); 1074 } 1075 break; 1076 case FORMAT_CONNECTION_STATUS: 1077 if (r->state == CON_STATE_RESPONSE_END) { 1078 if (r->keep_alive <= 0) { 1079 buffer_append_string_len(b, CONST_STR_LEN("-")); 1080 } else { 1081 buffer_append_string_len(b, CONST_STR_LEN("+")); 1082 } 1083 } else { /* CON_STATE_ERROR */ 1084 buffer_append_string_len(b, CONST_STR_LEN("X")); 1085 } 1086 break; 1087 case FORMAT_KEEPALIVE_COUNT: 1088 if (con->request_count > 1) { 1089 buffer_append_int(b, (intmax_t)(con->request_count-1)); 1090 } else { 1091 buffer_append_string_len(b, CONST_STR_LEN("0")); 1092 } 1093 break; 1094 case FORMAT_COOKIE: 1095 if (NULL != (vb = http_header_request_get(r, HTTP_HEADER_COOKIE, CONST_STR_LEN("Cookie")))) { 1096 char *str = vb->ptr; 1097 size_t len = buffer_string_length(&f->string); 1098 do { 1099 while (*str == ' ' || *str == '\t') ++str; 1100 if (0 == strncmp(str, f->string.ptr, len) && str[len] == '=') { 1101 char *v = str+len+1; 1102 for (str = v; *str != '\0' && *str != ';'; ++str) ; 1103 if (str == v) break; 1104 do { --str; } while (str > v && (*str == ' ' || *str == '\t')); 1105 accesslog_append_escaped_str(b, v, str - v + 1); 1106 break; 1107 } else { 1108 while (*str != ';' && *str != ' ' && *str != '\t' && *str != '\0') ++str; 1109 } 1110 while (*str == ' ' || *str == '\t') ++str; 1111 } while (*str++ == ';'); 1112 } 1113 break; 1114 default: 1115 break; 1116 } 1117 break; 1118 default: 1119 break; 1120 } 1121 } 1122 1123 return flush; 1124 } 1125 1126 REQUESTDONE_FUNC(log_access_write) { 1127 plugin_data * const p = p_d; 1128 mod_accesslog_patch_config(r, p); 1129 1130 /* No output device, nothing to do */ 1131 if (!p->conf.use_syslog && p->conf.log_access_fd == -1) return HANDLER_GO_ON; 1132 1133 buffer * const b = (p->conf.use_syslog) 1134 ? &p->syslog_logbuffer 1135 : p->conf.access_logbuffer; 1136 1137 const int flush = p->conf.piped_logger 1138 | log_access_record(r, b, p->conf.parsed_format); 1139 1140 if (p->conf.use_syslog) { /* syslog doesn't cache */ 1141 #ifdef HAVE_SYSLOG_H 1142 if (!buffer_string_is_empty(b)) { 1143 /*(syslog appends a \n on its own)*/ 1144 syslog(p->conf.syslog_level, "%s", b->ptr); 1145 } 1146 #endif 1147 buffer_clear(b); 1148 } 1149 else { 1150 buffer_append_string_len(b, CONST_STR_LEN("\n")); 1151 1152 if (flush || buffer_string_length(b) >= 8192) { 1153 if (!accesslog_write_all(p->conf.log_access_fd, b)) { 1154 log_perror(r->conf.errh, __FILE__, __LINE__, 1155 "writing access log entry failed: %s", 1156 p->conf.access_logfile->ptr); 1157 } 1158 } 1159 } 1160 1161 return HANDLER_GO_ON; 1162 } 1163 1164 1165 int mod_accesslog_plugin_init(plugin *p); 1166 int mod_accesslog_plugin_init(plugin *p) { 1167 p->version = LIGHTTPD_VERSION_ID; 1168 p->name = "accesslog"; 1169 1170 p->init = mod_accesslog_init; 1171 p->set_defaults= mod_accesslog_set_defaults; 1172 p->cleanup = mod_accesslog_free; 1173 1174 p->handle_request_done = log_access_write; 1175 p->handle_trigger = log_access_periodic_flush; 1176 p->handle_sighup = log_access_cycle; 1177 1178 return 0; 1179 } 1180