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