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