diff --git a/apisix/plugins/file-logger.lua b/apisix/plugins/file-logger.lua index afe73d662b79..f7201f5337d7 100644 --- a/apisix/plugins/file-logger.lua +++ b/apisix/plugins/file-logger.lua @@ -21,6 +21,8 @@ local plugin = require("apisix.plugin") local expr = require("resty.expr.v1") local ngx = ngx local io_open = io.open +local io_stdout = io.stdout +local io_stderr = io.stderr local is_apisix_or, process = pcall(require, "resty.apisix.process") @@ -89,6 +91,14 @@ local _M = { } +local std_files = { + ["/dev/stdout"] = io_stdout, + ["/dev/stderr"] = io_stderr, +} +io_stdout:setvbuf("no") +io_stderr:setvbuf("no") + + local function get_configured_path(conf) if conf.path then return conf.path @@ -185,7 +195,10 @@ local function write_file_data(conf, log_message) local file, err local file_conf = conf.path and conf or {path = path} - if open_file_cache then + local std_file = std_files[path] + if std_file then + file = std_file + elseif open_file_cache then file, err = open_file_cache(file_conf) else file, err = io_open(path, 'a+') @@ -204,8 +217,9 @@ local function write_file_data(conf, log_message) core.log.error("failed to write file: ", path, ", error info: ", err) end + -- stdout/stderr should not be closed -- file will be closed by gc, if open_file_cache exists - if not open_file_cache then + if not open_file_cache and not std_file then file:close() end end diff --git a/docs/en/latest/plugins/file-logger.md b/docs/en/latest/plugins/file-logger.md index 183ee55b6268..92b6ee0388e3 100644 --- a/docs/en/latest/plugins/file-logger.md +++ b/docs/en/latest/plugins/file-logger.md @@ -45,7 +45,7 @@ The `file-logger` Plugin is used to push log streams to a specific location. | Name | Type | Required | Description | | ---- | ------ | -------- | ------------- | -| path | string | True | Log file path. | +| path | string | True | Log file path. You can use `/dev/stdout` to write logs to the standard output and `/dev/stderr` to write to the standard error output. | | log_format | object | False | Log format declared as key-value pairs in JSON. Values support strings and nested objects (up to five levels deep; deeper fields are truncated). Within strings, [APISIX](../apisix-variable.md) or [NGINX](http://nginx.org/en/docs/varindex.html) variables can be referenced by prefixing with `$`. | | include_req_body | boolean | False | When set to `true` includes the request body in the log. If the request body is too big to be kept in the memory, it can't be logged due to Nginx's limitations. | | include_req_body_expr | array | False | Filter for when the `include_req_body` attribute is set to `true`. Request body is only logged when the expression set here evaluates to `true`. See [lua-resty-expr](https://github.com/api7/lua-resty-expr) for more. | diff --git a/docs/zh/latest/plugins/file-logger.md b/docs/zh/latest/plugins/file-logger.md index 3509b697ffcc..a128e574bfe2 100644 --- a/docs/zh/latest/plugins/file-logger.md +++ b/docs/zh/latest/plugins/file-logger.md @@ -47,7 +47,7 @@ description: API 网关 Apache APISIX file-logger 插件可用于将日志数据 | 名称 | 类型 | 必选项 | 描述 | | ---------------- | ------- |-----| ------------------------------------------------ | -| path | string | 是 | 自定义输出文件路径。例如:`logs/file.log`。 | +| path | string | 是 | 自定义输出文件路径。例如:`logs/file.log`。可以使用 `/dev/stdout` 将日志写入标准输出,使用 `/dev/stderr` 将日志写入标准错误输出。 | | log_format | object | 否 | 日志格式以 JSON 的键值对声明。值支持字符串和嵌套对象(最多五层,超出部分将被截断)。字符串中可通过在前面加上 `$` 来引用 [APISIX 变量](../apisix-variable.md) 或 [NGINX 内置变量](http://nginx.org/en/docs/varindex.html)。 | | include_req_body | boolean | 否 | 当设置为 `true` 时,日志中将包含请求体。如果请求体太大而无法在内存中保存,则由于 Nginx 的限制,无法记录请求体。| | include_req_body_expr | array | 否 | 当 `include_req_body` 属性设置为 `true` 时的过滤器。只有当此处设置的表达式求值为 `true` 时,才会记录请求体。有关更多信息,请参阅 [lua-resty-expr](https://github.com/api7/lua-resty-expr) 。 | diff --git a/t/cli/test_file_logger_stdout.sh b/t/cli/test_file_logger_stdout.sh new file mode 100755 index 000000000000..681c1237ed48 --- /dev/null +++ b/t/cli/test_file_logger_stdout.sh @@ -0,0 +1,77 @@ +#!/usr/bin/env bash + +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +. ./t/cli/common.sh + +# file-logger: verify logs are written to stdout via "/dev/stdout". +# Uses openresty directly (like docker-entrypoint.sh) because `apisix start` +# consumes stdout via io.popen, making /dev/stdout output unobservable. + +echo ' +apisix: + node_listen: 9080 +deployment: + role: data_plane + role_data_plane: + config_provider: yaml +' > conf/config.yaml + +echo ' +routes: + - uri: /hello + upstream: + nodes: + "127.0.0.1:1980": 1 + type: roundrobin + plugins: + file-logger: + path: /dev/stdout +#END +' > conf/apisix.yaml + +make init +# `apisix start` would create the logs directory; since we launch openresty +# directly, create it ourselves so nginx can open its error log. +mkdir -p logs + +# run openresty directly in the foreground and capture its stdout +openresty -p "$PWD" -g 'daemon off;' > stdout.log 2> stderr.log & +apisix_pid=$! + +# wait until APISIX is ready to serve +for _ in $(seq 1 20); do + if curl -s -o /dev/null http://127.0.0.1:9080/hello; then + break + fi + sleep 0.5 +done + +curl -s http://127.0.0.1:9080/hello > /dev/null +sleep 1 + +kill "$apisix_pid" 2>/dev/null || true +wait "$apisix_pid" 2>/dev/null || true + +if ! grep -q '"uri":"/hello"' stdout.log; then + echo "failed: file-logger did not write to stdout" + cat stdout.log + exit 1 +fi + +echo "passed: file-logger writes to stdout"