diff --git a/sapi/fpm/fpm/fpm_conf.c b/sapi/fpm/fpm/fpm_conf.c index 197c5b1c2b26e..5d088df501275 100644 --- a/sapi/fpm/fpm/fpm_conf.c +++ b/sapi/fpm/fpm/fpm_conf.c @@ -557,11 +557,13 @@ static char *fpm_conf_set_array(zval *key, zval *value, void **config, int conve } memset(kv, 0, sizeof(*kv)); - kv->key = strdup(Z_STRVAL_P(key)); + if (key) { + kv->key = strdup(Z_STRVAL_P(key)); - if (!kv->key) { - free(kv); - return "fpm_conf_set_array: strdup(key) failed"; + if (!kv->key) { + free(kv); + return "fpm_conf_set_array: strdup(key) failed"; + } } if (convert_to_bool) { @@ -663,6 +665,11 @@ int fpm_worker_pool_config_free(struct fpm_worker_pool_config_s *wpc) /* {{{ */ free(wpc->apparmor_hat); #endif + for (kv = wpc->access_suppress_paths; kv; kv = kv_next) { + kv_next = kv->next; + free(kv->value); + free(kv); + } for (kv = wpc->php_values; kv; kv = kv_next) { kv_next = kv->next; free(kv->key); @@ -1497,11 +1504,24 @@ static void fpm_conf_ini_parser_array(zval *name, zval *key, zval *value, void * char *err = NULL; void *config; - if (!*Z_STRVAL_P(key)) { - zlog(ZLOG_ERROR, "[%s:%d] Misspelled array ?", ini_filename, ini_lineno); + if (zend_string_equals_literal(Z_STR_P(name), "access.suppress_path")) { + if (!(*Z_STRVAL_P(key) == '\0')) { + zlog(ZLOG_ERROR, "[%s:%d] Keys provided to field 'access.suppress_path' are ignored", ini_filename, ini_lineno); + *error = 1; + } + if (!(*Z_STRVAL_P(value)) || (*Z_STRVAL_P(value) != '/')) { + zlog(ZLOG_ERROR, "[%s:%d] Values provided to field 'access.suppress_path' must begin with '/'", ini_filename, ini_lineno); + *error = 1; + } + if (*error) { + return; + } + } else if (!*Z_STRVAL_P(key)) { + zlog(ZLOG_ERROR, "[%s:%d] You must provide a key for field '%s'", ini_filename, ini_lineno, Z_STRVAL_P(name)); *error = 1; return; } + if (!current_wp || !current_wp->config) { zlog(ZLOG_ERROR, "[%s:%d] Array are not allowed in the global section", ini_filename, ini_lineno); *error = 1; @@ -1533,6 +1553,10 @@ static void fpm_conf_ini_parser_array(zval *name, zval *key, zval *value, void * config = (char *)current_wp->config + WPO(php_admin_values); err = fpm_conf_set_array(key, value, &config, 1); + } else if (zend_string_equals_literal(Z_STR_P(name), "access.suppress_path")) { + config = (char *)current_wp->config + WPO(access_suppress_paths); + err = fpm_conf_set_array(NULL, value, &config, 0); + } else { zlog(ZLOG_ERROR, "[%s:%d] unknown directive '%s'", ini_filename, ini_lineno, Z_STRVAL_P(name)); *error = 1; @@ -1735,6 +1759,9 @@ static void fpm_conf_dump(void) /* {{{ */ zlog(ZLOG_NOTICE, "\tping.response = %s", STR2STR(wp->config->ping_response)); zlog(ZLOG_NOTICE, "\taccess.log = %s", STR2STR(wp->config->access_log)); zlog(ZLOG_NOTICE, "\taccess.format = %s", STR2STR(wp->config->access_format)); + for (kv = wp->config->access_suppress_paths; kv; kv = kv->next) { + zlog(ZLOG_NOTICE, "\taccess.suppress_path[] = %s", kv->value); + } zlog(ZLOG_NOTICE, "\tslowlog = %s", STR2STR(wp->config->slowlog)); zlog(ZLOG_NOTICE, "\trequest_slowlog_timeout = %ds", wp->config->request_slowlog_timeout); zlog(ZLOG_NOTICE, "\trequest_slowlog_trace_depth = %d", wp->config->request_slowlog_trace_depth); diff --git a/sapi/fpm/fpm/fpm_conf.h b/sapi/fpm/fpm/fpm_conf.h index 4c0addc2a950f..47116376bdfdc 100644 --- a/sapi/fpm/fpm/fpm_conf.h +++ b/sapi/fpm/fpm/fpm_conf.h @@ -79,6 +79,7 @@ struct fpm_worker_pool_config_s { char *ping_response; char *access_log; char *access_format; + struct key_value_s *access_suppress_paths; char *slowlog; int request_slowlog_timeout; int request_slowlog_trace_depth; diff --git a/sapi/fpm/fpm/fpm_log.c b/sapi/fpm/fpm/fpm_log.c index d90d98b545683..bb66c081258d5 100644 --- a/sapi/fpm/fpm/fpm_log.c +++ b/sapi/fpm/fpm/fpm_log.c @@ -27,6 +27,9 @@ static char *fpm_log_format = NULL; static int fpm_log_fd = -1; +static struct key_value_s *fpm_access_suppress_paths = NULL; + +static int fpm_access_log_suppress(struct fpm_scoreboard_proc_s *proc); int fpm_log_open(int reopen) /* {{{ */ { @@ -79,6 +82,17 @@ int fpm_log_init_child(struct fpm_worker_pool_s *wp) /* {{{ */ } } + for (struct key_value_s *kv = wp->config->access_suppress_paths; kv; kv = kv->next) { + struct key_value_s *kvcopy = calloc(1, sizeof(*kvcopy)); + if (kvcopy == NULL) { + zlog(ZLOG_ERROR, "unable to allocate memory while opening the access log"); + return -1; + } + kvcopy->value = strdup(kv->value); + kvcopy->next = fpm_access_suppress_paths; + fpm_access_suppress_paths = kvcopy; + } + if (fpm_log_fd == -1) { fpm_log_fd = wp->log_fd; } @@ -136,6 +150,10 @@ int fpm_log_write(char *log_format) /* {{{ */ } proc = *proc_p; fpm_scoreboard_proc_release(proc_p); + + if (UNEXPECTED(fpm_access_log_suppress(&proc))) { + return -1; + } } token = 0; @@ -474,3 +492,38 @@ int fpm_log_write(char *log_format) /* {{{ */ return 0; } /* }}} */ + +static int fpm_access_log_suppress(struct fpm_scoreboard_proc_s *proc) +{ + // Never suppress when query string is passed + if (proc->query_string[0] != '\0') { + return 0; + } + + // Never suppress if request method is not GET or HEAD + if ( + strcmp(proc->request_method, "GET") != 0 + && strcmp(proc->request_method, "HEAD") != 0 + ) { + return 0; + } + + // Never suppress when response code does not indicate success + if (SG(sapi_headers).http_response_code < 200 || SG(sapi_headers).http_response_code > 299) { + return 0; + } + + // Never suppress when a body has been sent + if (SG(request_info).content_length > 0) { + return 0; + } + + // Suppress when request URI is an exact match for one of our entries + for (struct key_value_s *kv = fpm_access_suppress_paths; kv; kv = kv->next) { + if (kv->value && strcmp(kv->value, proc->request_uri) == 0) { + return 1; + } + } + + return 0; +} \ No newline at end of file diff --git a/sapi/fpm/tests/config-array-validation-php-value-key.phpt b/sapi/fpm/tests/config-array-validation-php-value-key.phpt new file mode 100644 index 0000000000000..53daec70c3828 --- /dev/null +++ b/sapi/fpm/tests/config-array-validation-php-value-key.phpt @@ -0,0 +1,34 @@ +--TEST-- +FPM: Validates arrays in configuration are correctly set - php_value array must be passed a key +--SKIPIF-- + +--FILE-- +start(['-tt']); +$tester->expectLogError("\[%s:%d\] You must provide a key for field 'php_value'"); + +?> +Done +--EXPECT-- + +Done +--CLEAN-- + diff --git a/sapi/fpm/tests/config-array-validation-suppress-path-key-2.phpt b/sapi/fpm/tests/config-array-validation-suppress-path-key-2.phpt new file mode 100644 index 0000000000000..68ef0c322a146 --- /dev/null +++ b/sapi/fpm/tests/config-array-validation-suppress-path-key-2.phpt @@ -0,0 +1,34 @@ +--TEST-- +FPM: Validates arrays in configuration are correctly set - access.suppress_path doesn't accept key with forward slash +--SKIPIF-- + +--FILE-- +start(['-tt']); +$tester->expectLogError("\[%s:%d\] Keys provided to field 'access.suppress_path' are ignored"); + +?> +Done +--EXPECT-- + +Done +--CLEAN-- + diff --git a/sapi/fpm/tests/config-array-validation-suppress-path-key.phpt b/sapi/fpm/tests/config-array-validation-suppress-path-key.phpt new file mode 100644 index 0000000000000..5b7a141a14a7b --- /dev/null +++ b/sapi/fpm/tests/config-array-validation-suppress-path-key.phpt @@ -0,0 +1,34 @@ +--TEST-- +FPM: Validates arrays in configuration are correctly set - access.suppress_path doesn't allow key +--SKIPIF-- + +--FILE-- +start(['-tt']); +$tester->expectLogError("\[%s:%d\] Keys provided to field 'access.suppress_path' are ignored"); + +?> +Done +--EXPECT-- + +Done +--CLEAN-- + diff --git a/sapi/fpm/tests/config-array-validation-suppress-path-starts-slash.phpt b/sapi/fpm/tests/config-array-validation-suppress-path-starts-slash.phpt new file mode 100644 index 0000000000000..400c3621a85a9 --- /dev/null +++ b/sapi/fpm/tests/config-array-validation-suppress-path-starts-slash.phpt @@ -0,0 +1,34 @@ +--TEST-- +FPM: Validates arrays in configuration are correctly set - access.suppress_path begins with forward slash +--SKIPIF-- + +--FILE-- +start(['-tt']); +$tester->expectLogError("\[%s:%d\] Values provided to field 'access.suppress_path' must begin with '\/'"); + +?> +Done +--EXPECT-- + +Done +--CLEAN-- + diff --git a/sapi/fpm/tests/config-array.phpt b/sapi/fpm/tests/config-array.phpt new file mode 100644 index 0000000000000..f2ddabac4b7f0 --- /dev/null +++ b/sapi/fpm/tests/config-array.phpt @@ -0,0 +1,49 @@ +--TEST-- +FPM: Set arrays in configuration +--SKIPIF-- + +--FILE-- +start(['-tt']); +$tester->expectLogConfigOptions([ + 'access.suppress_path[] = /ping', + 'access.suppress_path[] = /health_check.php', + 'php_value[error_reporting] = 32767', + 'php_value[date.timezone] = Europe/London', + 'php_value[display_errors] = 1', + 'php_admin_value[disable_functions] = eval', + 'php_admin_value[log_errors] = 1', +]); + + +?> +Done +--EXPECT-- + +Done +--CLEAN-- + diff --git a/sapi/fpm/tests/log-suppress-output-request-body.phpt b/sapi/fpm/tests/log-suppress-output-request-body.phpt new file mode 100644 index 0000000000000..c878b40f964cb --- /dev/null +++ b/sapi/fpm/tests/log-suppress-output-request-body.phpt @@ -0,0 +1,107 @@ +--TEST-- +FPM: Test URIs are not excluded from access log when there is a request body +--SKIPIF-- + +--FILE-- +start(); +$tester->expectLogStartNotices(); +$tester->expectSuppressableAccessLogEntries(false); +$tester->ping(); + +// Should not suppress POST with no body +$tester->request( + uri: '/request-1', + headers: ['REQUEST_METHOD' => 'POST'] +)->expectBody('0'); +$tester->expectAccessLog("'POST /request-1' 200", suppressable: false); + +// Should not suppress POST with body +$tester->request( + uri: '/request-2', + stdin: $body +)->expectBody('100'); +$tester->expectAccessLog("'POST /request-2' 200", suppressable: false); + +// Should not suppress GET with body +$tester->request( + uri: '/request-3', + headers: ['REQUEST_METHOD' => 'GET'], + stdin: $body +)->expectBody('100'); +$tester->expectAccessLog("'GET /request-3' 200", suppressable: false); + +// Should suppress GET with no body +$tester->request( + uri: '/request-4' +)->expectBody('0'); +$tester->expectAccessLog("'GET /request-4' 200", suppressable: true); + +// Should not suppress GET with no body but incorrect content length +$tester->request( + uri: '/request-5', + headers: ['REQUEST_METHOD' => 'GET', 'CONTENT_LENGTH' => 100] +)->expectBody('0'); +$tester->expectAccessLog("'GET /request-5' 200", suppressable: false); + +// Should suppress GET with body but 0 content length (no stdin readable) +$tester->request( + uri: '/request-6', + headers: ['REQUEST_METHOD' => 'GET', 'CONTENT_LENGTH' => 0], + stdin: $body +)->expectBody('0'); +$tester->expectAccessLog("'GET /request-6' 200", suppressable: true); + +$tester->terminate(); +$tester->expectLogTerminatingNotices(); +$tester->close(); +$tester->expectNoFile(FPM\Tester::FILE_EXT_PID); +$tester->checkAccessLog(); + +?> +Done +--EXPECT-- +Done +--CLEAN-- + diff --git a/sapi/fpm/tests/log-suppress-output.phpt b/sapi/fpm/tests/log-suppress-output.phpt new file mode 100644 index 0000000000000..5a5e7bb9544ba --- /dev/null +++ b/sapi/fpm/tests/log-suppress-output.phpt @@ -0,0 +1,126 @@ +--TEST-- +FPM: Test excluding URIs from access log +--SKIPIF-- + +--FILE-- +expectSuppressableAccessLogEntries($expectSuppressableEntries); + + $tester->ping(); + $tester->expectAccessLog("'GET /ping' 200", suppressable: true); + + $tester->request()->expectBody('OK'); + $tester->expectAccessLog("'GET /log-suppress-output.src.php' 200", suppressable: true); + + $tester->ping(); + $tester->expectAccessLog("'GET /ping' 200", suppressable: true); + + $tester->request()->expectBody('OK'); + $tester->expectAccessLog("'GET /log-suppress-output.src.php' 200", suppressable: true); + + $tester->ping(); + $tester->expectAccessLog("'GET /ping' 200", suppressable: true); + + $tester->request(query: 'test=output')->expectBody('output'); + $tester->expectAccessLog("'GET /log-suppress-output.src.php?test=output' 200", suppressable: false); + + $tester->ping(); + $tester->expectAccessLog("'GET /ping' 200", suppressable: true); + + $tester->request()->expectBody('OK'); + $tester->expectAccessLog("'GET /log-suppress-output.src.php' 200", suppressable: true); + + $tester->request(query: 'test=output', uri: '/ping')->expectBody('pong', 'text/plain'); + $tester->expectAccessLog("'GET /ping?test=output' 200", suppressable: false); + + $tester->request(headers: ['X_ERROR' => 1])->expectBody('Not OK'); + $tester->expectAccessLog("'GET /log-suppress-output.src.php' 500", suppressable: false); + + $tester->request()->expectBody('OK'); + $tester->expectAccessLog("'GET /log-suppress-output.src.php' 200", suppressable: true); + + $tester->request(query: 'test=output', uri: '/ping')->expectBody('pong', 'text/plain'); + $tester->expectAccessLog("'GET /ping?test=output' 200", suppressable: false); + + $tester->ping(); + $tester->expectAccessLog("'GET /ping' 200", suppressable: true); +} + +$src = <<start(['--prefix', $prefix]); +$tester->expectLogStartNotices(); +doTestCalls($tester, expectSuppressableEntries: true); +// Add source file and ping to ignore list +$cfg = <<reload($cfg); +$tester->expectLogReloadingNotices(); +doTestCalls($tester, expectSuppressableEntries: false); +$tester->terminate(); +$tester->expectLogTerminatingNotices(); +$tester->close(); +$tester->expectNoFile(FPM\Tester::FILE_EXT_PID, $prefix); +$tester->checkAccessLog(); + +?> +Done +--EXPECT-- +Done +--CLEAN-- + diff --git a/sapi/fpm/tests/logtool.inc b/sapi/fpm/tests/logtool.inc index e085ae291beab..9555212683e58 100644 --- a/sapi/fpm/tests/logtool.inc +++ b/sapi/fpm/tests/logtool.inc @@ -363,8 +363,13 @@ class LogTool $expectedMessage = '\[pool ' . $pool . '\] ' . $expectedMessage; } + // Allow expected message to contain %s and %s for any string or number + // as in run-tests.php + $expectRe = str_replace('%s', '[^\r\n]+', $expectedMessage); + $expectRe = str_replace('%d', '\d+', $expectRe); + $line = rtrim($line); - $pattern = sprintf('/^(%s )?%s: %s$/', self::P_TIME, $type, $expectedMessage); + $pattern = sprintf('/^(%s )?%s: %s$/', self::P_TIME, $type, $expectRe); if (preg_match($pattern, $line, $matches) === 0) { return $this->error( diff --git a/sapi/fpm/tests/pm-max-spawn-rate-config.phpt b/sapi/fpm/tests/pm-max-spawn-rate-config.phpt index 8b5f6b4eb6404..999c1d747092d 100644 --- a/sapi/fpm/tests/pm-max-spawn-rate-config.phpt +++ b/sapi/fpm/tests/pm-max-spawn-rate-config.phpt @@ -24,8 +24,8 @@ pm.max_spawn_rate = 64 EOT; $tester = new FPM\Tester($cfg); -$tester->start(['-t', '-t']); -$tester->expectLogConfigOptions(['pm.max_spawn_rate' => 64]); +$tester->start(['-tt']); +$tester->expectLogConfigOptions(['pm.max_spawn_rate = 64']); $tester->close(); ?> diff --git a/sapi/fpm/tests/tester.inc b/sapi/fpm/tests/tester.inc index e607035df1b31..0956dd969aaba 100644 --- a/sapi/fpm/tests/tester.inc +++ b/sapi/fpm/tests/tester.inc @@ -120,6 +120,16 @@ class Tester */ private $response; + /** + * @var string[] + */ + private $expectedAccessLogs; + + /** + * @var bool + */ + private $expectSuppressableAccessLogEntries; + /** * Clean all the created files up * @@ -345,7 +355,7 @@ class Tester public function testConfig() { $configFile = $this->createConfig(); - $cmd = self::findExecutable() . ' -t -y ' . $configFile . ' 2>&1'; + $cmd = self::findExecutable() . ' -tt -y ' . $configFile . ' 2>&1'; exec($cmd, $output, $code); if ($code) { return preg_replace("/\[.+?\]/", "", $output[0]); @@ -534,16 +544,20 @@ class Tester string $query = '', array $headers = [], string $uri = null, - string $scriptFilename = null + string $scriptFilename = null, + ?string $stdin = null ) { + if (is_null($scriptFilename)) { + $scriptFilename = $this->makeSourceFile(); + } if (is_null($uri)) { - $uri = $this->makeSourceFile(); + $uri = "/" . basename($scriptFilename); } $params = array_merge( [ 'GATEWAY_INTERFACE' => 'FastCGI/1.0', - 'REQUEST_METHOD' => 'GET', + 'REQUEST_METHOD' => is_null($stdin) ? 'GET' : 'POST', 'SCRIPT_FILENAME' => $scriptFilename ?: $uri, 'SCRIPT_NAME' => $uri, 'QUERY_STRING' => $query, @@ -558,7 +572,7 @@ class Tester 'SERVER_PROTOCOL' => 'HTTP/1.1', 'DOCUMENT_ROOT' => __DIR__, 'CONTENT_TYPE' => '', - 'CONTENT_LENGTH' => 0 + 'CONTENT_LENGTH' => strlen($stdin ?? "") // Default to 0 ], $headers ); @@ -589,17 +603,18 @@ class Tester string $successMessage = null, string $errorMessage = null, bool $connKeepAlive = false, - string $scriptFilename = null + string $scriptFilename = null, + string $stdin = null ) { if ($this->hasError()) { return new Response(null, true); } - $params = $this->getRequestParams($query, $headers, $uri, $scriptFilename); + $params = $this->getRequestParams($query, $headers, $uri, $scriptFilename, $stdin); try { $this->response = new Response( - $this->getClient($address, $connKeepAlive)->request_data($params, false) + $this->getClient($address, $connKeepAlive)->request_data($params, $stdin) ); $this->message($successMessage); } catch (\Exception $exception) { @@ -1159,7 +1174,6 @@ class Tester { $filePath = $this->getFile($extension, $dir, $name); file_put_contents($filePath, $content); - return $filePath; } @@ -1437,18 +1451,28 @@ class Tester * @param array $options * @return bool */ - public function expectLogConfigOptions(array $options) + public function expectLogConfigOptions(array $expectedOptions) { $configOptions = $this->getConfigOptions(); - foreach ($options as $name => $value) { - if (!isset($configOptions[$name])) { - return $this->error("Expected config option: {$name} = {$value} but {$name} is not set"); + foreach ($expectedOptions as $expectedOption) { + if (array_search($expectedOption, $configOptions, true)) { + // Exact match found, no error + continue; } - if ($configOptions[$name] != $value) { - return $this->error( - "Expected config option: {$name} = {$value} but got: {$name} = {$configOptions[$name]}" - ); + + // Try to find similar key + $key = substr($expectedOption, 0, strpos($expectedOption, " = ")); + $matches = array_filter($configOptions, fn($configOption) => substr($configOption, 0, strlen($key)) == $key); + + if (empty($matches)) { + return $this->error("Expected config option: $expectedOption but {$key} is not set"); } + + return $this->error(sprintf( + "Expected config option: %s but got: %s", + $expectedOption, + implode("; ", $matches) + )); } return true; @@ -1462,14 +1486,13 @@ class Tester private function getConfigOptions() { $options = []; - foreach ($this->getLogLines(-1) as $line) { preg_match('/.+NOTICE:\s+(.+)\s=\s(.+)/', $line, $matches); if ($matches) { - $options[$matches[1]] = $matches[2]; + // normalize format for consistent checking + $options[] = sprintf("%s = %s", $matches[1], $matches[2]); } } - return $options; } @@ -1483,4 +1506,68 @@ class Tester print file_get_contents($accessLog); } } + + /** + * Return content of access log. + * + * @return string|false + */ + public function getAccessLog() + { + $accessLog = $this->getFile('acc.log'); + if (is_file($accessLog)) { + return file_get_contents($accessLog); + } + return false; + } + + /** + * Expect a single access log line. + * + * @param string $LogLine + * @param bool $suppressable see expectSuppressableAccessLogEntries + */ + public function expectAccessLog( + string $logLine, + bool $suppressable = false + ) { + if (!$suppressable || $this->expectSuppressableAccessLogEntries) { + $this->expectedAccessLogs[] = $logLine; + } + } + + /** + * Checks that all access log entries previously listed as expected by + * calling "expectAccessLog" are in the access log. + */ + public function checkAccessLog() + { + if (isset($this->expectedAccessLogs)) { + $expectedAccessLog = implode("\n", $this->expectedAccessLogs) . "\n"; + } else { + $this->error("Called checkAccessLog but did not previous call expectAccessLog"); + } + if ($accessLog = $this->getAccessLog()) { + if ($expectedAccessLog !== $accessLog) { + $this->error(sprintf( + "Access log was not as expected.\nEXPECTED:\n%s\n\nACTUAL:\n%s", + $expectedAccessLog, + $accessLog + )); + } + } else { + $this->error("Called checkAccessLog but access log does not exist"); + } + } + + /** + * Flags whether the access log check should expect to see suppressable + * log entries, i.e. the URL is not in access.suppress_path[] config + * + * @param bool + */ + public function expectSuppressableAccessLogEntries(bool $expectSuppressableAccessLogEntries) + { + $this->expectSuppressableAccessLogEntries = $expectSuppressableAccessLogEntries; + } } diff --git a/sapi/fpm/www.conf.in b/sapi/fpm/www.conf.in index 32705b9f3d378..fd774c6161295 100644 --- a/sapi/fpm/www.conf.in +++ b/sapi/fpm/www.conf.in @@ -343,6 +343,22 @@ pm.max_spare_servers = 3 ; Default: "%R - %u %t \"%m %r\" %s" ;access.format = "%R - %u %t \"%m %r%Q%q\" %s %f %{milli}d %{kilo}M %C%%" +; A list of request_uri values which should be filtered from the access log. +; +; As a security precuation, this setting will be ignored if: +; - the request method is not GET or HEAD; or +; - there is a request body; or +; - there are query parameters; or +; - the response code is outwith the successful range of 200 to 299 +; +; Note: The paths are matched against the output of the access.format tag "%r". +; On common configurations, this may look more like SCRIPT_NAME than the +; expected pre-rewrite URI. +; +; Default Value: not set +;access.suppress_path[] = /ping +;access.suppress_path[] = /health_check.php + ; The log file for slow requests ; Default Value: not set ; Note: slowlog is mandatory if request_slowlog_timeout is set