1 /* (c) 2009 Jerome Loyet */
2
3 #include "php.h"
4 #include "SAPI.h"
5 #include <stdio.h>
6 #include <unistd.h>
7 #include <fcntl.h>
8
9 #ifdef HAVE_TIMES
10 #include <sys/times.h>
11 #endif
12
13 #include "fpm_config.h"
14 #include "fpm_log.h"
15 #include "fpm_clock.h"
16 #include "fpm_process_ctl.h"
17 #include "fpm_signals.h"
18 #include "fpm_scoreboard.h"
19 #include "fastcgi.h"
20 #include "zlog.h"
21
22 #ifdef MAX_LINE_LENGTH
23 # define FPM_LOG_BUFFER MAX_LINE_LENGTH
24 #else
25 # define FPM_LOG_BUFFER 1024
26 #endif
27
28 static char *fpm_log_format = NULL;
29 static int fpm_log_fd = -1;
30 static struct key_value_s *fpm_access_suppress_paths = NULL;
31
32 static int fpm_access_log_suppress(struct fpm_scoreboard_proc_s *proc);
33
fpm_log_open(int reopen)34 int fpm_log_open(int reopen) /* {{{ */
35 {
36 struct fpm_worker_pool_s *wp;
37 int ret = 1;
38
39 int fd;
40 for (wp = fpm_worker_all_pools; wp; wp = wp->next) {
41 if (!wp->config->access_log) {
42 continue;
43 }
44 ret = 0;
45
46 fd = open(wp->config->access_log, O_WRONLY | O_APPEND | O_CREAT, S_IRUSR | S_IWUSR);
47 if (0 > fd) {
48 zlog(ZLOG_SYSERROR, "failed to open access log (%s)", wp->config->access_log);
49 return -1;
50 } else {
51 zlog(ZLOG_DEBUG, "open access log (%s)", wp->config->access_log);
52 }
53
54 if (reopen) {
55 dup2(fd, wp->log_fd);
56 close(fd);
57 fd = wp->log_fd;
58 fpm_pctl_kill_all(SIGQUIT);
59 } else {
60 wp->log_fd = fd;
61 }
62
63 if (0 > fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC)) {
64 zlog(ZLOG_WARNING, "failed to change attribute of access_log");
65 }
66 }
67
68 return ret;
69 }
70 /* }}} */
71
72 /* }}} */
fpm_log_init_child(struct fpm_worker_pool_s * wp)73 int fpm_log_init_child(struct fpm_worker_pool_s *wp) /* {{{ */
74 {
75 if (!wp || !wp->config) {
76 return -1;
77 }
78
79 if (wp->config->access_log && *wp->config->access_log) {
80 if (wp->config->access_format) {
81 fpm_log_format = strdup(wp->config->access_format);
82 }
83 }
84
85 for (struct key_value_s *kv = wp->config->access_suppress_paths; kv; kv = kv->next) {
86 struct key_value_s *kvcopy = calloc(1, sizeof(*kvcopy));
87 if (kvcopy == NULL) {
88 zlog(ZLOG_ERROR, "unable to allocate memory while opening the access log");
89 return -1;
90 }
91 kvcopy->value = strdup(kv->value);
92 kvcopy->next = fpm_access_suppress_paths;
93 fpm_access_suppress_paths = kvcopy;
94 }
95
96 if (fpm_log_fd == -1) {
97 fpm_log_fd = wp->log_fd;
98 }
99
100
101 for (wp = fpm_worker_all_pools; wp; wp = wp->next) {
102 if (wp->log_fd > -1 && wp->log_fd != fpm_log_fd) {
103 close(wp->log_fd);
104 wp->log_fd = -1;
105 }
106 }
107
108 return 0;
109 }
110 /* }}} */
111
fpm_log_write(char * log_format)112 int fpm_log_write(char *log_format) /* {{{ */
113 {
114 char *s, *b;
115 char buffer[FPM_LOG_BUFFER+1];
116 int token, test;
117 size_t len, len2;
118 struct fpm_scoreboard_proc_s proc, *proc_p;
119 struct fpm_scoreboard_s *scoreboard;
120 char tmp[129];
121 char format[129];
122 time_t now_epoch;
123 #ifdef HAVE_TIMES
124 clock_t tms_total;
125 #endif
126
127 if (!log_format && (!fpm_log_format || fpm_log_fd == -1)) {
128 return -1;
129 }
130
131 if (!log_format) {
132 log_format = fpm_log_format;
133 test = 0;
134 } else {
135 test = 1;
136 }
137
138 now_epoch = time(NULL);
139
140 if (!test) {
141 scoreboard = fpm_scoreboard_get();
142 if (!scoreboard) {
143 zlog(ZLOG_WARNING, "unable to get scoreboard while preparing the access log");
144 return -1;
145 }
146 proc_p = fpm_scoreboard_proc_acquire(NULL, -1, 0);
147 if (!proc_p) {
148 zlog(ZLOG_WARNING, "[pool %s] Unable to acquire shm slot while preparing the access log", scoreboard->pool);
149 return -1;
150 }
151 proc = *proc_p;
152 fpm_scoreboard_proc_release(proc_p);
153
154 if (UNEXPECTED(fpm_access_log_suppress(&proc))) {
155 return -1;
156 }
157 }
158
159 token = 0;
160
161 memset(buffer, '\0', sizeof(buffer));
162 b = buffer;
163 len = 0;
164
165
166 s = log_format;
167
168 while (*s != '\0') {
169 /* Test is we have place for 1 more char. */
170 if (len >= FPM_LOG_BUFFER) {
171 zlog(ZLOG_NOTICE, "the log buffer is full (%d). The access log request has been truncated.", FPM_LOG_BUFFER);
172 len = FPM_LOG_BUFFER;
173 break;
174 }
175
176 if (!token && *s == '%') {
177 token = 1;
178 memset(format, '\0', sizeof(format)); /* reset format */
179 s++;
180 continue;
181 }
182
183 if (token) {
184 token = 0;
185 len2 = 0;
186 switch (*s) {
187
188 case '%': /* '%' */
189 *b = '%';
190 len2 = 1;
191 break;
192
193 #ifdef HAVE_TIMES
194 case 'C': /* %CPU */
195 if (format[0] == '\0' || !strcasecmp(format, "total")) {
196 if (!test) {
197 tms_total = proc.last_request_cpu.tms_utime + proc.last_request_cpu.tms_stime + proc.last_request_cpu.tms_cutime + proc.last_request_cpu.tms_cstime;
198 }
199 } else if (!strcasecmp(format, "user")) {
200 if (!test) {
201 tms_total = proc.last_request_cpu.tms_utime + proc.last_request_cpu.tms_cutime;
202 }
203 } else if (!strcasecmp(format, "system")) {
204 if (!test) {
205 tms_total = proc.last_request_cpu.tms_stime + proc.last_request_cpu.tms_cstime;
206 }
207 } else {
208 zlog(ZLOG_WARNING, "only 'total', 'user' or 'system' are allowed as a modifier for %%%c ('%s')", *s, format);
209 return -1;
210 }
211
212 format[0] = '\0';
213 if (!test) {
214 len2 = snprintf(b, FPM_LOG_BUFFER - len, "%.2f", tms_total / fpm_scoreboard_get_tick() / (proc.cpu_duration.tv_sec + proc.cpu_duration.tv_usec / 1000000.) * 100.);
215 }
216 break;
217 #endif
218
219 case 'd': /* duration µs */
220 /* seconds */
221 if (format[0] == '\0' || !strcasecmp(format, "seconds")) {
222 if (!test) {
223 len2 = snprintf(b, FPM_LOG_BUFFER - len, "%.3f", proc.duration.tv_sec + proc.duration.tv_usec / 1000000.);
224 }
225
226 /* milliseconds */
227 } else if (!strcasecmp(format, "milliseconds") || !strcasecmp(format, "milli") ||
228 /* mili/miliseconds are supported for backwards compatibility */
229 !strcasecmp(format, "miliseconds") || !strcasecmp(format, "mili")
230 ) {
231 if (!test) {
232 len2 = snprintf(b, FPM_LOG_BUFFER - len, "%.3f", proc.duration.tv_sec * 1000. + proc.duration.tv_usec / 1000.);
233 }
234
235 /* microseconds */
236 } else if (!strcasecmp(format, "microseconds") || !strcasecmp(format, "micro")) {
237 if (!test) {
238 len2 = snprintf(b, FPM_LOG_BUFFER - len, "%lu", (unsigned long)(proc.duration.tv_sec * 1000000UL + proc.duration.tv_usec));
239 }
240
241 } else {
242 zlog(ZLOG_WARNING, "only 'seconds', 'milli', 'milliseconds', 'micro' or 'microseconds' are allowed as a modifier for %%%c ('%s')", *s, format);
243 return -1;
244 }
245 format[0] = '\0';
246 break;
247
248 case 'e': /* fastcgi env */
249 if (format[0] == '\0') {
250 zlog(ZLOG_WARNING, "the name of the environment variable must be set between embraces for %%%c", *s);
251 return -1;
252 }
253
254 if (!test) {
255 char *env = fcgi_getenv((fcgi_request*) SG(server_context), format, strlen(format));
256 len2 = snprintf(b, FPM_LOG_BUFFER - len, "%s", env ? env : "-");
257 }
258 format[0] = '\0';
259 break;
260
261 case 'f': /* script */
262 if (!test) {
263 len2 = snprintf(b, FPM_LOG_BUFFER - len, "%s", *proc.script_filename ? proc.script_filename : "-");
264 }
265 break;
266
267 case 'l': /* content length */
268 if (!test) {
269 len2 = snprintf(b, FPM_LOG_BUFFER - len, "%zu", proc.content_length);
270 }
271 break;
272
273 case 'm': /* method */
274 if (!test) {
275 len2 = snprintf(b, FPM_LOG_BUFFER - len, "%s", *proc.request_method ? proc.request_method : "-");
276 }
277 break;
278
279 case 'M': /* memory */
280 /* seconds */
281 if (format[0] == '\0' || !strcasecmp(format, "bytes")) {
282 if (!test) {
283 len2 = snprintf(b, FPM_LOG_BUFFER - len, "%zu", proc.memory);
284 }
285
286 /* kilobytes */
287 } else if (!strcasecmp(format, "kilobytes") || !strcasecmp(format, "kilo")) {
288 if (!test) {
289 len2 = snprintf(b, FPM_LOG_BUFFER - len, "%zu", proc.memory / 1024);
290 }
291
292 /* megabytes */
293 } else if (!strcasecmp(format, "megabytes") || !strcasecmp(format, "mega")) {
294 if (!test) {
295 len2 = snprintf(b, FPM_LOG_BUFFER - len, "%zu", proc.memory / 1024 / 1024);
296 }
297
298 } else {
299 zlog(ZLOG_WARNING, "only 'bytes', 'kilo', 'kilobytes', 'mega' or 'megabytes' are allowed as a modifier for %%%c ('%s')", *s, format);
300 return -1;
301 }
302 format[0] = '\0';
303 break;
304
305 case 'n': /* pool name */
306 if (!test) {
307 len2 = snprintf(b, FPM_LOG_BUFFER - len, "%s", scoreboard->pool[0] ? scoreboard->pool : "-");
308 }
309 break;
310
311 case 'o': /* header output */
312 if (format[0] == '\0') {
313 zlog(ZLOG_WARNING, "the name of the header must be set between embraces for %%%c", *s);
314 return -1;
315 }
316 if (!test) {
317 sapi_header_struct *h;
318 zend_llist_position pos;
319 sapi_headers_struct *sapi_headers = &SG(sapi_headers);
320 size_t format_len = strlen(format);
321
322 h = (sapi_header_struct*)zend_llist_get_first_ex(&sapi_headers->headers, &pos);
323 while (h) {
324 char *header;
325 if (!h->header_len) {
326 h = (sapi_header_struct*)zend_llist_get_next_ex(&sapi_headers->headers, &pos);
327 continue;
328 }
329 if (!strstr(h->header, format)) {
330 h = (sapi_header_struct*)zend_llist_get_next_ex(&sapi_headers->headers, &pos);
331 continue;
332 }
333
334 /* test if enough char after the header name + ': ' */
335 if (h->header_len <= format_len + 2) {
336 h = (sapi_header_struct*)zend_llist_get_next_ex(&sapi_headers->headers, &pos);
337 continue;
338 }
339
340 if (h->header[format_len] != ':' || h->header[format_len + 1] != ' ') {
341 h = (sapi_header_struct*)zend_llist_get_next_ex(&sapi_headers->headers, &pos);
342 continue;
343 }
344
345 header = h->header + format_len + 2;
346 len2 = snprintf(b, FPM_LOG_BUFFER - len, "%s", header && *header ? header : "-");
347
348 /* found, done */
349 break;
350 }
351 if (!len2) {
352 len2 = 1;
353 *b = '-';
354 }
355 }
356 format[0] = '\0';
357 break;
358
359 case 'p': /* PID */
360 if (!test) {
361 len2 = snprintf(b, FPM_LOG_BUFFER - len, "%ld", (long)getpid());
362 }
363 break;
364
365 case 'P': /* PID */
366 if (!test) {
367 len2 = snprintf(b, FPM_LOG_BUFFER - len, "%ld", (long)getppid());
368 }
369 break;
370
371 case 'q': /* query_string */
372 if (!test) {
373 len2 = snprintf(b, FPM_LOG_BUFFER - len, "%s", proc.query_string);
374 }
375 break;
376
377 case 'Q': /* '?' */
378 if (!test) {
379 len2 = snprintf(b, FPM_LOG_BUFFER - len, "%s", *proc.query_string ? "?" : "");
380 }
381 break;
382
383 case 'r': /* request URI */
384 if (!test) {
385 len2 = snprintf(b, FPM_LOG_BUFFER - len, "%s", proc.request_uri);
386 }
387 break;
388
389 case 'R': /* remote IP address */
390 if (!test) {
391 const char *tmp = fcgi_get_last_client_ip();
392 len2 = snprintf(b, FPM_LOG_BUFFER - len, "%s", tmp ? tmp : "-");
393 }
394 break;
395
396 case 's': /* status */
397 if (!test) {
398 len2 = snprintf(b, FPM_LOG_BUFFER - len, "%d", SG(sapi_headers).http_response_code);
399 }
400 break;
401
402 case 'T':
403 case 't': /* time */
404 if (!test) {
405 time_t *t;
406 if (*s == 't') {
407 t = &proc.accepted_epoch;
408 } else {
409 t = &now_epoch;
410 }
411 if (format[0] == '\0') {
412 strftime(tmp, sizeof(tmp) - 1, "%d/%b/%Y:%H:%M:%S %z", localtime(t));
413 } else {
414 strftime(tmp, sizeof(tmp) - 1, format, localtime(t));
415 }
416 len2 = snprintf(b, FPM_LOG_BUFFER - len, "%s", tmp);
417 }
418 format[0] = '\0';
419 break;
420
421 case 'u': /* remote user */
422 if (!test) {
423 len2 = snprintf(b, FPM_LOG_BUFFER - len, "%s", proc.auth_user);
424 }
425 break;
426
427 case '{': /* complex var */
428 token = 1;
429 {
430 char *start;
431 size_t l;
432
433 start = ++s;
434
435 while (*s != '\0') {
436 if (*s == '}') {
437 l = s - start;
438
439 if (l >= sizeof(format) - 1) {
440 l = sizeof(format) - 1;
441 }
442
443 memcpy(format, start, l);
444 format[l] = '\0';
445 break;
446 }
447 s++;
448 }
449 if (s[1] == '\0') {
450 zlog(ZLOG_WARNING, "missing closing embrace in the access.format");
451 return -1;
452 }
453 }
454 break;
455
456 default:
457 zlog(ZLOG_WARNING, "Invalid token in the access.format (%%%c)", *s);
458 return -1;
459 }
460
461 if (*s != '}' && format[0] != '\0') {
462 zlog(ZLOG_WARNING, "embrace is not allowed for modifier %%%c", *s);
463 return -1;
464 }
465 s++;
466 if (!test) {
467 b += len2;
468 len += len2;
469 }
470 if (len >= FPM_LOG_BUFFER) {
471 zlog(ZLOG_NOTICE, "the log buffer is full (%d). The access log request has been truncated.", FPM_LOG_BUFFER);
472 len = FPM_LOG_BUFFER;
473 break;
474 }
475 continue;
476 }
477
478 if (!test) {
479 // push the normal char to the output buffer
480 *b = *s;
481 b++;
482 len++;
483 }
484 s++;
485 }
486
487 if (!test && strlen(buffer) > 0) {
488 buffer[len] = '\n';
489 zend_quiet_write(fpm_log_fd, buffer, len + 1);
490 }
491
492 return 0;
493 }
494 /* }}} */
495
fpm_access_log_suppress(struct fpm_scoreboard_proc_s * proc)496 static int fpm_access_log_suppress(struct fpm_scoreboard_proc_s *proc)
497 {
498 // Never suppress when query string is passed
499 if (proc->query_string[0] != '\0') {
500 return 0;
501 }
502
503 // Never suppress if request method is not GET or HEAD
504 if (
505 strcmp(proc->request_method, "GET") != 0
506 && strcmp(proc->request_method, "HEAD") != 0
507 ) {
508 return 0;
509 }
510
511 // Never suppress when response code does not indicate success
512 if (SG(sapi_headers).http_response_code < 200 || SG(sapi_headers).http_response_code > 299) {
513 return 0;
514 }
515
516 // Never suppress when a body has been sent
517 if (SG(request_info).content_length > 0) {
518 return 0;
519 }
520
521 // Suppress when request URI is an exact match for one of our entries
522 for (struct key_value_s *kv = fpm_access_suppress_paths; kv; kv = kv->next) {
523 if (kv->value && strcmp(kv->value, proc->request_uri) == 0) {
524 return 1;
525 }
526 }
527
528 return 0;
529 }