You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@apisix.apache.org by GitBox <gi...@apache.org> on 2021/11/21 12:00:08 UTC

[GitHub] [apisix] spacewander commented on a change in pull request #5538: feat(plugin): support google cloud logging service

spacewander commented on a change in pull request #5538:
URL: https://github.com/apache/apisix/pull/5538#discussion_r753787079



##########
File path: apisix/utils/log-util.lua
##########
@@ -94,6 +94,7 @@ local function get_full_log(ngx, conf)
 
     local log =  {
         request = {
+            id = var.request_id,

Review comment:
       Better not add a new field to the common library without full discussion.

##########
File path: apisix/core/utils.lua
##########
@@ -260,6 +260,18 @@ function _M.gethostname()
 end
 
 
+function _M.get_file(file_name)

Review comment:
       Could we put it into core/io.lua and let core.request refer it?

##########
File path: apisix/plugins/google-logging.lua
##########
@@ -0,0 +1,298 @@
+--
+-- 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.
+--
+
+local core = require("apisix.core")
+local ngx = ngx
+local tostring = tostring
+local ipairs = ipairs
+local sub_str = string.sub
+local os_date = os.date
+
+local ngx_now = ngx.now
+local ngx_timer_at = ngx.timer.at
+local ngx_update_time = ngx.update_time
+
+local http = require("resty.http")
+local log_util = require("apisix.utils.log-util")
+local batch_processor = require("apisix.utils.batch-processor")
+local google_oauth = require("apisix.plugins.google-logging.oauth")
+
+
+local buffers = {}
+local auth_config_cache
+local stale_timer_running
+
+
+local plugin_name = "google-logging"
+local schema = {
+    type = "object",
+    properties = {
+        auth_config = {
+            type = "object",
+            properties = {
+                private_key = { type = "string" },
+                project_id = { type = "string" },
+                token_uri = {
+                    type = "string",
+                    default = "https://oauth2.googleapis.com/token"
+                },
+                -- https://developers.google.com/identity/protocols/oauth2/scopes#logging
+                scopes = {
+                    type = "array",
+                    items = {
+                        description = "Google OAuth2 Authorization Scopes",
+                        type = "string",
+                    },
+                    minItems = 1,
+                    uniqueItems = true,
+                    default = {
+                        "https://www.googleapis.com/auth/logging.read",
+                        "https://www.googleapis.com/auth/logging.write",
+                        "https://www.googleapis.com/auth/logging.admin",
+                        "https://www.googleapis.com/auth/cloud-platform"
+                    }
+                },
+                entries_uri = {
+                    type = "string",
+                    default = "https://logging.googleapis.com/v2/entries:write"
+                },
+            },
+            required = { "private_key", "project_id", "token_uri" }
+        },
+        auth_file = { type = "string" },
+        -- https://cloud.google.com/logging/docs/reference/v2/rest/v2/MonitoredResource
+        resource = {
+            type = "object",
+            properties = {
+                type = { type = "string" },
+                labels = { type = "object" }
+            },
+            default = {
+                type = "global"
+            },
+            required = { "type" }
+        },
+        -- https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry
+        log_id = { type = "string", default = "apisix.apache.org%2Flogs" },
+        max_retry_count = { type = "integer", minimum = 0, default = 0 },
+        retry_delay = { type = "integer", minimum = 0, default = 1 },
+        buffer_duration = { type = "integer", minimum = 1, default = 60 },
+        inactive_timeout = { type = "integer", minimum = 1, default = 10 },
+        batch_max_size = { type = "integer", minimum = 1, default = 100 },
+    },
+    oneOf = {
+        { required = { "auth_config" } },
+        { required = { "auth_file" } },
+    },
+}
+
+
+-- remove stale objects from the memory after timer expires
+local function remove_stale_objects(premature)
+    if premature then
+        return
+    end
+
+    for key, batch in ipairs(buffers) do
+        if #batch.entry_buffer.entries == 0 and #batch.batch_to_process == 0 then
+            core.log.warn("removing batch processor stale object, route id:", tostring(key))
+            buffers[key] = nil
+        end
+    end
+
+    stale_timer_running = false
+end
+
+
+local function send_to_google(oauth, entries)
+    local http_new = http.new()
+    local res, err = http_new:request_uri(oauth.entries_uri, {
+        ssl_verify = false,

Review comment:
       We need to make ssl_verify configurable and default to true.

##########
File path: docs/en/latest/plugins/google-logging.md
##########
@@ -0,0 +1,155 @@
+---
+title: google-logging
+---
+
+<!--
+#
+# 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.
+#
+-->
+
+## Summary
+
+- [**Name**](#name)
+- [**Attributes**](#attributes)
+- [**How To Enable**](#how-to-enable)
+- [**Test Plugin**](#test-plugin)
+- [**Disable Plugin**](#disable-plugin)
+
+## Name
+
+`google-logging` plugin is used to send the access log of `Apache APISIX` to the [Google Cloud Logging Service](https://cloud.google.com/logging/).
+
+This plugin provides the ability to push Log data as a batch to Google Cloud logging Service.
+
+For more info on Batch-Processor in Apache APISIX please refer:
+[Batch-Processor](../batch-processor.md)
+
+## Attributes
+
+| Name                    | Requirement   | Default                                                                                                                                                                                           | Description                                                                                                                                                                      |
+| ----------------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| auth_config             | Semi-optional |                                                                                                                                                                                                   | one of `auth_config` or `auth_file` must be configured                                                                                                                           |
+| auth_config.private_key | Required      |                                                                                                                                                                                                   | the private key parameters of the Google service account                                                                                                                         |
+| auth_config.project_id  | Required      |                                                                                                                                                                                                   | the project id parameters of the Google service account                                                                                                                          |
+| auth_config.token_uri   | Optional      | https://oauth2.googleapis.com/token                                                                                                                                                               | the token uri parameters of the Google service account                                                                                                                           |
+| auth_config.entries_uri | Optional      | https://logging.googleapis.com/v2/entries:write                                                                                                                                                   | google logging service API                                                                                                                                                       |
+| auth_config.scopes      | Optional      | ["https://www.googleapis.com/auth/logging.read","https://www.googleapis.com/auth/logging.write","https://www.googleapis.com/auth/logging.admin","https://www.googleapis.com/auth/cloud-platform"] | the access scopes parameters of the Google service account, refer to: [OAuth 2.0 Scopes for Google APIs](https://developers.google.com/identity/protocols/oauth2/scopes#logging) |
+| auth_file               | Semi-optional |                                                                                                                                                                                                   | path to the google service account json file(Semi-optional, one of auth_config or auth_file must be configured)                                                              |
+| resource                | Optional      | {"type": "global"}                                                                                                                                                                                | the Google monitor resource, refer to: [MonitoredResource](https://cloud.google.com/logging/docs/reference/v2/rest/v2/MonitoredResource)                                         |
+| log_id                  | Optional      | apisix.apache.org%2Flogs                                                                                                                                                                          | google logging id, refer to: [LogEntry](https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry)                                                                     |
+| max_retry_count         | Optional      | 0                                                                                                                                                                                                 | max number of retries before removing from the processing pipe line                                                                                                              |
+| retry_delay             | Optional      | 1                                                                                                                                                                                                 | number of seconds the process execution should be delayed if the execution fails                                                                                                 |
+| buffer_duration         | Optional      | 60                                                                                                                                                                                                | max age in seconds of the oldest entry in a batch before the batch must be processed                                                                                             |
+| inactive_timeout        | Optional      | 10                                                                                                                                                                                                | max age in seconds when the buffer will be flushed if inactive                                                                                                                   |
+| batch_max_size          | Optional      | 100                                                                                                                                                                                               | max size of each batch                                                                                                                                                           |
+
+## How To Enable
+
+The following is an example on how to enable the `google-logging` for a specific route.
+
+### Full configuration
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "plugins": {
+        "google-logging": {
+            "auth_config":{
+                "project_id":"apisix",
+                "private_key":"-----BEGIN RSA PRIVATE KEY-----KEY-----END RSA PRIVATE KEY-----",

Review comment:
       Could we use a valid pkey?

##########
File path: apisix/plugins/google-logging.lua
##########
@@ -0,0 +1,298 @@
+--
+-- 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.
+--
+
+local core = require("apisix.core")
+local ngx = ngx
+local tostring = tostring
+local ipairs = ipairs
+local sub_str = string.sub
+local os_date = os.date
+
+local ngx_now = ngx.now
+local ngx_timer_at = ngx.timer.at
+local ngx_update_time = ngx.update_time
+
+local http = require("resty.http")
+local log_util = require("apisix.utils.log-util")
+local batch_processor = require("apisix.utils.batch-processor")
+local google_oauth = require("apisix.plugins.google-logging.oauth")
+
+
+local buffers = {}
+local auth_config_cache
+local stale_timer_running
+
+
+local plugin_name = "google-logging"
+local schema = {
+    type = "object",
+    properties = {
+        auth_config = {
+            type = "object",
+            properties = {
+                private_key = { type = "string" },
+                project_id = { type = "string" },
+                token_uri = {
+                    type = "string",
+                    default = "https://oauth2.googleapis.com/token"
+                },
+                -- https://developers.google.com/identity/protocols/oauth2/scopes#logging
+                scopes = {
+                    type = "array",
+                    items = {
+                        description = "Google OAuth2 Authorization Scopes",
+                        type = "string",
+                    },
+                    minItems = 1,
+                    uniqueItems = true,
+                    default = {
+                        "https://www.googleapis.com/auth/logging.read",
+                        "https://www.googleapis.com/auth/logging.write",
+                        "https://www.googleapis.com/auth/logging.admin",
+                        "https://www.googleapis.com/auth/cloud-platform"
+                    }
+                },
+                entries_uri = {
+                    type = "string",
+                    default = "https://logging.googleapis.com/v2/entries:write"
+                },
+            },
+            required = { "private_key", "project_id", "token_uri" }
+        },
+        auth_file = { type = "string" },
+        -- https://cloud.google.com/logging/docs/reference/v2/rest/v2/MonitoredResource
+        resource = {
+            type = "object",
+            properties = {
+                type = { type = "string" },
+                labels = { type = "object" }
+            },
+            default = {
+                type = "global"
+            },
+            required = { "type" }
+        },
+        -- https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry
+        log_id = { type = "string", default = "apisix.apache.org%2Flogs" },
+        max_retry_count = { type = "integer", minimum = 0, default = 0 },
+        retry_delay = { type = "integer", minimum = 0, default = 1 },
+        buffer_duration = { type = "integer", minimum = 1, default = 60 },
+        inactive_timeout = { type = "integer", minimum = 1, default = 10 },
+        batch_max_size = { type = "integer", minimum = 1, default = 100 },
+    },
+    oneOf = {
+        { required = { "auth_config" } },
+        { required = { "auth_file" } },
+    },
+}
+
+
+-- remove stale objects from the memory after timer expires
+local function remove_stale_objects(premature)
+    if premature then
+        return
+    end
+
+    for key, batch in ipairs(buffers) do
+        if #batch.entry_buffer.entries == 0 and #batch.batch_to_process == 0 then
+            core.log.warn("removing batch processor stale object, route id:", tostring(key))
+            buffers[key] = nil
+        end
+    end
+
+    stale_timer_running = false
+end
+
+
+local function send_to_google(oauth, entries)
+    local http_new = http.new()
+    local res, err = http_new:request_uri(oauth.entries_uri, {
+        ssl_verify = false,
+        method = "POST",
+        body = core.json.encode({
+            entries = entries,
+            partialSuccess = false,
+        }),
+        headers = {
+            ["Content-Type"] = "application/json",
+            ["Authorization"] = "Bearer " .. oauth:get_access_token(),
+        },
+    })
+
+    if err then
+        return nil, "failed to write log to google, ", err
+    end
+
+    if res.status ~= 200 then
+        return nil, res.body
+    end
+
+    return res.body
+end
+
+
+local function get_auth_config(config)
+    local err
+    local auth_config = {}
+    if config.auth_config then
+        auth_config = config.auth_config
+    end
+
+    if config.auth_file then
+        local file_content
+        file_content, err = core.utils.get_file(config.auth_file)
+        if file_content then
+            auth_config = core.json.decode(file_content)
+        end
+    end
+
+    if err then
+        return nil, err
+    end
+
+    return auth_config
+end
+
+
+local function get_logger_buffer(conf, ctx)
+    local oauth_client = google_oauth:new(auth_config_cache)
+
+    local process = function(entries)
+        return send_to_google(oauth_client, entries)
+    end
+
+    local config = {
+        name = conf.name or plugin_name,
+        retry_delay = conf.retry_delay,
+        batch_max_size = conf.batch_max_size,
+        max_retry_count = conf.max_retry_count,
+        buffer_duration = conf.buffer_duration,
+        inactive_timeout = conf.inactive_timeout,
+        route_id = ctx.var.route_id,
+        server_addr = ctx.var.server_addr,
+    }
+
+    local buffer, err = batch_processor:new(process, config)
+
+    if not buffer then
+        return nil, "error when creating the batch processor: " .. err
+    end
+
+    return buffer
+end
+
+
+local function get_utc_timestamp()
+    ngx_update_time()
+    local now = tostring(ngx_now())
+    local pos = core.string.rfind_char(now, ".", #now - 1)
+    local second = now
+    local millisecond = 0
+    if pos then
+        second = sub_str(now, 1, pos - 1)
+        millisecond = sub_str(now, pos + 1)
+    end
+    return os_date("!%Y-%m-%dT%T.", second) .. core.string.format("%03dZ", millisecond)
+end
+
+
+local function get_logger_entry(conf)
+    if not auth_config_cache then

Review comment:
       It would be better to associate the auth config cache into get_auth_config.

##########
File path: Makefile
##########
@@ -303,6 +303,9 @@ install: runtime
 	$(ENV_INSTALL) -d $(ENV_INST_LUADIR)/apisix/plugins/limit-count
 	$(ENV_INSTALL) apisix/plugins/limit-count/*.lua $(ENV_INST_LUADIR)/apisix/plugins/limit-count/
 
+	$(ENV_INSTALL) -d $(ENV_INST_LUADIR)/apisix/plugins/google-logging
+	$(ENV_INSTALL) apisix/plugins/google-logging/*.lua $(ENV_INST_LUADIR)/apisix/plugins/google-logging/

Review comment:
       We should use the name `google-cloud-logging` instead of google-logging? It is confusing if we don't mention which google product we are working with.

##########
File path: apisix/plugins/google-logging.lua
##########
@@ -0,0 +1,298 @@
+--
+-- 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.
+--
+
+local core = require("apisix.core")
+local ngx = ngx
+local tostring = tostring
+local ipairs = ipairs
+local sub_str = string.sub
+local os_date = os.date
+
+local ngx_now = ngx.now
+local ngx_timer_at = ngx.timer.at
+local ngx_update_time = ngx.update_time
+
+local http = require("resty.http")
+local log_util = require("apisix.utils.log-util")
+local batch_processor = require("apisix.utils.batch-processor")
+local google_oauth = require("apisix.plugins.google-logging.oauth")
+
+
+local buffers = {}
+local auth_config_cache
+local stale_timer_running
+
+
+local plugin_name = "google-logging"
+local schema = {
+    type = "object",
+    properties = {
+        auth_config = {
+            type = "object",
+            properties = {
+                private_key = { type = "string" },
+                project_id = { type = "string" },
+                token_uri = {
+                    type = "string",
+                    default = "https://oauth2.googleapis.com/token"
+                },
+                -- https://developers.google.com/identity/protocols/oauth2/scopes#logging
+                scopes = {
+                    type = "array",
+                    items = {
+                        description = "Google OAuth2 Authorization Scopes",
+                        type = "string",
+                    },
+                    minItems = 1,
+                    uniqueItems = true,
+                    default = {
+                        "https://www.googleapis.com/auth/logging.read",
+                        "https://www.googleapis.com/auth/logging.write",
+                        "https://www.googleapis.com/auth/logging.admin",
+                        "https://www.googleapis.com/auth/cloud-platform"
+                    }
+                },
+                entries_uri = {
+                    type = "string",
+                    default = "https://logging.googleapis.com/v2/entries:write"
+                },
+            },
+            required = { "private_key", "project_id", "token_uri" }
+        },
+        auth_file = { type = "string" },
+        -- https://cloud.google.com/logging/docs/reference/v2/rest/v2/MonitoredResource
+        resource = {
+            type = "object",
+            properties = {
+                type = { type = "string" },
+                labels = { type = "object" }
+            },
+            default = {
+                type = "global"
+            },
+            required = { "type" }
+        },
+        -- https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry
+        log_id = { type = "string", default = "apisix.apache.org%2Flogs" },
+        max_retry_count = { type = "integer", minimum = 0, default = 0 },
+        retry_delay = { type = "integer", minimum = 0, default = 1 },
+        buffer_duration = { type = "integer", minimum = 1, default = 60 },
+        inactive_timeout = { type = "integer", minimum = 1, default = 10 },
+        batch_max_size = { type = "integer", minimum = 1, default = 100 },
+    },
+    oneOf = {
+        { required = { "auth_config" } },
+        { required = { "auth_file" } },
+    },
+}
+
+
+-- remove stale objects from the memory after timer expires
+local function remove_stale_objects(premature)
+    if premature then
+        return
+    end
+
+    for key, batch in ipairs(buffers) do
+        if #batch.entry_buffer.entries == 0 and #batch.batch_to_process == 0 then
+            core.log.warn("removing batch processor stale object, route id:", tostring(key))
+            buffers[key] = nil
+        end
+    end
+
+    stale_timer_running = false
+end
+
+
+local function send_to_google(oauth, entries)
+    local http_new = http.new()
+    local res, err = http_new:request_uri(oauth.entries_uri, {
+        ssl_verify = false,
+        method = "POST",
+        body = core.json.encode({
+            entries = entries,
+            partialSuccess = false,
+        }),
+        headers = {
+            ["Content-Type"] = "application/json",
+            ["Authorization"] = "Bearer " .. oauth:get_access_token(),
+        },
+    })
+
+    if err then
+        return nil, "failed to write log to google, ", err
+    end
+
+    if res.status ~= 200 then
+        return nil, res.body
+    end
+
+    return res.body
+end
+
+
+local function get_auth_config(config)
+    local err
+    local auth_config = {}
+    if config.auth_config then
+        auth_config = config.auth_config
+    end
+
+    if config.auth_file then
+        local file_content
+        file_content, err = core.utils.get_file(config.auth_file)
+        if file_content then
+            auth_config = core.json.decode(file_content)
+        end
+    end
+
+    if err then
+        return nil, err
+    end
+
+    return auth_config
+end
+
+
+local function get_logger_buffer(conf, ctx)
+    local oauth_client = google_oauth:new(auth_config_cache)
+
+    local process = function(entries)
+        return send_to_google(oauth_client, entries)
+    end
+
+    local config = {
+        name = conf.name or plugin_name,
+        retry_delay = conf.retry_delay,
+        batch_max_size = conf.batch_max_size,
+        max_retry_count = conf.max_retry_count,
+        buffer_duration = conf.buffer_duration,
+        inactive_timeout = conf.inactive_timeout,
+        route_id = ctx.var.route_id,
+        server_addr = ctx.var.server_addr,
+    }
+
+    local buffer, err = batch_processor:new(process, config)
+
+    if not buffer then
+        return nil, "error when creating the batch processor: " .. err
+    end
+
+    return buffer
+end
+
+
+local function get_utc_timestamp()
+    ngx_update_time()
+    local now = tostring(ngx_now())
+    local pos = core.string.rfind_char(now, ".", #now - 1)
+    local second = now
+    local millisecond = 0
+    if pos then
+        second = sub_str(now, 1, pos - 1)
+        millisecond = sub_str(now, pos + 1)
+    end
+    return os_date("!%Y-%m-%dT%T.", second) .. core.string.format("%03dZ", millisecond)
+end
+
+
+local function get_logger_entry(conf)
+    if not auth_config_cache then
+        local auth_config, err = get_auth_config(conf)
+        if err or not auth_config.project_id or not auth_config.private_key then
+            return nil, "failed to get google authentication configuration" .. err
+        end
+
+        auth_config_cache = auth_config
+    end
+
+    local entry = log_util.get_full_log(ngx, conf)
+    local google_entry = {
+        httpRequest = {
+            requestMethod = entry.request.method,
+            requestUrl = entry.request.url,
+            requestSize = entry.request.size,
+            status = entry.response.status,
+            responseSize = entry.response.size,
+            userAgent = entry.request.headers and entry.request.headers["user-agent"],
+            remoteIp = entry.client_ip,
+            serverIp = entry.upstream,
+            latency = tostring(core.string.format("%0.3f", entry.latency / 1000)) .. "s"
+        },
+        jsonPayload = {
+            route_id = entry.route_id,
+            service_id = entry.service_id,
+        },
+        labels = {
+            source = "apache-apisix-google-logging"
+        },
+        timestamp = get_utc_timestamp(),
+        resource = conf.resource,
+        insertId = entry.request.id,

Review comment:
       Can we add the var.request_id directly?

##########
File path: docs/en/latest/plugins/google-logging.md
##########
@@ -0,0 +1,155 @@
+---
+title: google-logging
+---
+
+<!--
+#
+# 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.
+#
+-->
+
+## Summary
+
+- [**Name**](#name)
+- [**Attributes**](#attributes)
+- [**How To Enable**](#how-to-enable)
+- [**Test Plugin**](#test-plugin)
+- [**Disable Plugin**](#disable-plugin)
+
+## Name
+
+`google-logging` plugin is used to send the access log of `Apache APISIX` to the [Google Cloud Logging Service](https://cloud.google.com/logging/).
+
+This plugin provides the ability to push Log data as a batch to Google Cloud logging Service.

Review comment:
       ```suggestion
   This plugin provides the ability to push log data as a batch to Google Cloud logging Service.
   ```

##########
File path: apisix/plugins/google-logging.lua
##########
@@ -0,0 +1,298 @@
+--
+-- 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.
+--
+
+local core = require("apisix.core")
+local ngx = ngx
+local tostring = tostring
+local ipairs = ipairs
+local sub_str = string.sub
+local os_date = os.date
+
+local ngx_now = ngx.now
+local ngx_timer_at = ngx.timer.at
+local ngx_update_time = ngx.update_time
+
+local http = require("resty.http")
+local log_util = require("apisix.utils.log-util")
+local batch_processor = require("apisix.utils.batch-processor")
+local google_oauth = require("apisix.plugins.google-logging.oauth")
+
+
+local buffers = {}
+local auth_config_cache
+local stale_timer_running
+
+
+local plugin_name = "google-logging"
+local schema = {
+    type = "object",
+    properties = {
+        auth_config = {
+            type = "object",
+            properties = {
+                private_key = { type = "string" },
+                project_id = { type = "string" },
+                token_uri = {
+                    type = "string",
+                    default = "https://oauth2.googleapis.com/token"
+                },
+                -- https://developers.google.com/identity/protocols/oauth2/scopes#logging
+                scopes = {
+                    type = "array",
+                    items = {
+                        description = "Google OAuth2 Authorization Scopes",
+                        type = "string",
+                    },
+                    minItems = 1,
+                    uniqueItems = true,
+                    default = {
+                        "https://www.googleapis.com/auth/logging.read",
+                        "https://www.googleapis.com/auth/logging.write",
+                        "https://www.googleapis.com/auth/logging.admin",
+                        "https://www.googleapis.com/auth/cloud-platform"
+                    }
+                },
+                entries_uri = {
+                    type = "string",
+                    default = "https://logging.googleapis.com/v2/entries:write"
+                },
+            },
+            required = { "private_key", "project_id", "token_uri" }
+        },
+        auth_file = { type = "string" },
+        -- https://cloud.google.com/logging/docs/reference/v2/rest/v2/MonitoredResource
+        resource = {
+            type = "object",
+            properties = {
+                type = { type = "string" },
+                labels = { type = "object" }
+            },
+            default = {
+                type = "global"
+            },
+            required = { "type" }
+        },
+        -- https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry
+        log_id = { type = "string", default = "apisix.apache.org%2Flogs" },
+        max_retry_count = { type = "integer", minimum = 0, default = 0 },
+        retry_delay = { type = "integer", minimum = 0, default = 1 },
+        buffer_duration = { type = "integer", minimum = 1, default = 60 },
+        inactive_timeout = { type = "integer", minimum = 1, default = 10 },
+        batch_max_size = { type = "integer", minimum = 1, default = 100 },
+    },
+    oneOf = {
+        { required = { "auth_config" } },
+        { required = { "auth_file" } },
+    },
+}
+
+
+-- remove stale objects from the memory after timer expires
+local function remove_stale_objects(premature)
+    if premature then
+        return
+    end
+
+    for key, batch in ipairs(buffers) do
+        if #batch.entry_buffer.entries == 0 and #batch.batch_to_process == 0 then
+            core.log.warn("removing batch processor stale object, route id:", tostring(key))
+            buffers[key] = nil
+        end
+    end
+
+    stale_timer_running = false
+end
+
+
+local function send_to_google(oauth, entries)
+    local http_new = http.new()
+    local res, err = http_new:request_uri(oauth.entries_uri, {
+        ssl_verify = false,
+        method = "POST",
+        body = core.json.encode({
+            entries = entries,
+            partialSuccess = false,
+        }),
+        headers = {
+            ["Content-Type"] = "application/json",
+            ["Authorization"] = "Bearer " .. oauth:get_access_token(),
+        },
+    })
+
+    if err then
+        return nil, "failed to write log to google, ", err
+    end
+
+    if res.status ~= 200 then
+        return nil, res.body
+    end
+
+    return res.body
+end
+
+
+local function get_auth_config(config)
+    local err
+    local auth_config = {}
+    if config.auth_config then
+        auth_config = config.auth_config
+    end
+
+    if config.auth_file then
+        local file_content
+        file_content, err = core.utils.get_file(config.auth_file)
+        if file_content then
+            auth_config = core.json.decode(file_content)

Review comment:
       Better to check the decode err.

##########
File path: docs/en/latest/plugins/google-logging.md
##########
@@ -0,0 +1,155 @@
+---
+title: google-logging
+---
+
+<!--
+#
+# 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.
+#
+-->
+
+## Summary
+
+- [**Name**](#name)
+- [**Attributes**](#attributes)
+- [**How To Enable**](#how-to-enable)
+- [**Test Plugin**](#test-plugin)
+- [**Disable Plugin**](#disable-plugin)
+
+## Name
+
+`google-logging` plugin is used to send the access log of `Apache APISIX` to the [Google Cloud Logging Service](https://cloud.google.com/logging/).
+
+This plugin provides the ability to push Log data as a batch to Google Cloud logging Service.
+
+For more info on Batch-Processor in Apache APISIX please refer:
+[Batch-Processor](../batch-processor.md)
+
+## Attributes
+
+| Name                    | Requirement   | Default                                                                                                                                                                                           | Description                                                                                                                                                                      |
+| ----------------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| auth_config             | Semi-optional |                                                                                                                                                                                                   | one of `auth_config` or `auth_file` must be configured                                                                                                                           |
+| auth_config.private_key | Required      |                                                                                                                                                                                                   | the private key parameters of the Google service account                                                                                                                         |
+| auth_config.project_id  | Required      |                                                                                                                                                                                                   | the project id parameters of the Google service account                                                                                                                          |
+| auth_config.token_uri   | Optional      | https://oauth2.googleapis.com/token                                                                                                                                                               | the token uri parameters of the Google service account                                                                                                                           |
+| auth_config.entries_uri | Optional      | https://logging.googleapis.com/v2/entries:write                                                                                                                                                   | google logging service API                                                                                                                                                       |
+| auth_config.scopes      | Optional      | ["https://www.googleapis.com/auth/logging.read","https://www.googleapis.com/auth/logging.write","https://www.googleapis.com/auth/logging.admin","https://www.googleapis.com/auth/cloud-platform"] | the access scopes parameters of the Google service account, refer to: [OAuth 2.0 Scopes for Google APIs](https://developers.google.com/identity/protocols/oauth2/scopes#logging) |
+| auth_file               | Semi-optional |                                                                                                                                                                                                   | path to the google service account json file(Semi-optional, one of auth_config or auth_file must be configured)                                                              |
+| resource                | Optional      | {"type": "global"}                                                                                                                                                                                | the Google monitor resource, refer to: [MonitoredResource](https://cloud.google.com/logging/docs/reference/v2/rest/v2/MonitoredResource)                                         |
+| log_id                  | Optional      | apisix.apache.org%2Flogs                                                                                                                                                                          | google logging id, refer to: [LogEntry](https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry)                                                                     |
+| max_retry_count         | Optional      | 0                                                                                                                                                                                                 | max number of retries before removing from the processing pipe line                                                                                                              |
+| retry_delay             | Optional      | 1                                                                                                                                                                                                 | number of seconds the process execution should be delayed if the execution fails                                                                                                 |
+| buffer_duration         | Optional      | 60                                                                                                                                                                                                | max age in seconds of the oldest entry in a batch before the batch must be processed                                                                                             |
+| inactive_timeout        | Optional      | 10                                                                                                                                                                                                | max age in seconds when the buffer will be flushed if inactive                                                                                                                   |
+| batch_max_size          | Optional      | 100                                                                                                                                                                                               | max size of each batch                                                                                                                                                           |
+
+## How To Enable
+
+The following is an example on how to enable the `google-logging` for a specific route.
+
+### Full configuration
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "plugins": {
+        "google-logging": {
+            "auth_config":{
+                "project_id":"apisix",
+                "private_key":"-----BEGIN RSA PRIVATE KEY-----KEY-----END RSA PRIVATE KEY-----",
+                "token_uri":"https://oauth2.googleapis.com/token",
+                "scopes":[
+                    "https://www.googleapis.com/auth/logging.admin"
+                ],
+                "entries_uri":"https://logging.googleapis.com/v2/entries:write"
+            },
+            "resource":{
+                "type":"global"
+            },
+            "log_id":"syslog",

Review comment:
       The log_id is incorrect?

##########
File path: docs/en/latest/plugins/google-logging.md
##########
@@ -0,0 +1,155 @@
+---
+title: google-logging
+---
+
+<!--
+#
+# 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.
+#
+-->
+
+## Summary
+
+- [**Name**](#name)
+- [**Attributes**](#attributes)
+- [**How To Enable**](#how-to-enable)
+- [**Test Plugin**](#test-plugin)
+- [**Disable Plugin**](#disable-plugin)
+
+## Name
+
+`google-logging` plugin is used to send the access log of `Apache APISIX` to the [Google Cloud Logging Service](https://cloud.google.com/logging/).
+
+This plugin provides the ability to push Log data as a batch to Google Cloud logging Service.
+
+For more info on Batch-Processor in Apache APISIX please refer:
+[Batch-Processor](../batch-processor.md)
+
+## Attributes
+
+| Name                    | Requirement   | Default                                                                                                                                                                                           | Description                                                                                                                                                                      |
+| ----------------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| auth_config             | Semi-optional |                                                                                                                                                                                                   | one of `auth_config` or `auth_file` must be configured                                                                                                                           |
+| auth_config.private_key | Required      |                                                                                                                                                                                                   | the private key parameters of the Google service account                                                                                                                         |
+| auth_config.project_id  | Required      |                                                                                                                                                                                                   | the project id parameters of the Google service account                                                                                                                          |
+| auth_config.token_uri   | Optional      | https://oauth2.googleapis.com/token                                                                                                                                                               | the token uri parameters of the Google service account                                                                                                                           |
+| auth_config.entries_uri | Optional      | https://logging.googleapis.com/v2/entries:write                                                                                                                                                   | google logging service API                                                                                                                                                       |
+| auth_config.scopes      | Optional      | ["https://www.googleapis.com/auth/logging.read","https://www.googleapis.com/auth/logging.write","https://www.googleapis.com/auth/logging.admin","https://www.googleapis.com/auth/cloud-platform"] | the access scopes parameters of the Google service account, refer to: [OAuth 2.0 Scopes for Google APIs](https://developers.google.com/identity/protocols/oauth2/scopes#logging) |
+| auth_file               | Semi-optional |                                                                                                                                                                                                   | path to the google service account json file(Semi-optional, one of auth_config or auth_file must be configured)                                                              |
+| resource                | Optional      | {"type": "global"}                                                                                                                                                                                | the Google monitor resource, refer to: [MonitoredResource](https://cloud.google.com/logging/docs/reference/v2/rest/v2/MonitoredResource)                                         |
+| log_id                  | Optional      | apisix.apache.org%2Flogs                                                                                                                                                                          | google logging id, refer to: [LogEntry](https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry)                                                                     |
+| max_retry_count         | Optional      | 0                                                                                                                                                                                                 | max number of retries before removing from the processing pipe line                                                                                                              |
+| retry_delay             | Optional      | 1                                                                                                                                                                                                 | number of seconds the process execution should be delayed if the execution fails                                                                                                 |
+| buffer_duration         | Optional      | 60                                                                                                                                                                                                | max age in seconds of the oldest entry in a batch before the batch must be processed                                                                                             |
+| inactive_timeout        | Optional      | 10                                                                                                                                                                                                | max age in seconds when the buffer will be flushed if inactive                                                                                                                   |
+| batch_max_size          | Optional      | 100                                                                                                                                                                                               | max size of each batch                                                                                                                                                           |
+
+## How To Enable
+
+The following is an example on how to enable the `google-logging` for a specific route.

Review comment:
       ```suggestion
   The following is an example of how to enable the `google-logging` for a specific route.
   ```

##########
File path: apisix/plugins/google-logging.lua
##########
@@ -0,0 +1,298 @@
+--
+-- 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.
+--
+
+local core = require("apisix.core")
+local ngx = ngx
+local tostring = tostring
+local ipairs = ipairs
+local sub_str = string.sub
+local os_date = os.date
+
+local ngx_now = ngx.now
+local ngx_timer_at = ngx.timer.at
+local ngx_update_time = ngx.update_time
+
+local http = require("resty.http")
+local log_util = require("apisix.utils.log-util")
+local batch_processor = require("apisix.utils.batch-processor")
+local google_oauth = require("apisix.plugins.google-logging.oauth")
+
+
+local buffers = {}
+local auth_config_cache
+local stale_timer_running
+
+
+local plugin_name = "google-logging"
+local schema = {
+    type = "object",
+    properties = {
+        auth_config = {
+            type = "object",
+            properties = {
+                private_key = { type = "string" },
+                project_id = { type = "string" },
+                token_uri = {
+                    type = "string",
+                    default = "https://oauth2.googleapis.com/token"
+                },
+                -- https://developers.google.com/identity/protocols/oauth2/scopes#logging
+                scopes = {
+                    type = "array",
+                    items = {
+                        description = "Google OAuth2 Authorization Scopes",
+                        type = "string",
+                    },
+                    minItems = 1,
+                    uniqueItems = true,
+                    default = {
+                        "https://www.googleapis.com/auth/logging.read",
+                        "https://www.googleapis.com/auth/logging.write",
+                        "https://www.googleapis.com/auth/logging.admin",
+                        "https://www.googleapis.com/auth/cloud-platform"
+                    }
+                },
+                entries_uri = {
+                    type = "string",
+                    default = "https://logging.googleapis.com/v2/entries:write"
+                },
+            },
+            required = { "private_key", "project_id", "token_uri" }
+        },
+        auth_file = { type = "string" },
+        -- https://cloud.google.com/logging/docs/reference/v2/rest/v2/MonitoredResource
+        resource = {
+            type = "object",
+            properties = {
+                type = { type = "string" },
+                labels = { type = "object" }
+            },
+            default = {
+                type = "global"
+            },
+            required = { "type" }
+        },
+        -- https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry
+        log_id = { type = "string", default = "apisix.apache.org%2Flogs" },
+        max_retry_count = { type = "integer", minimum = 0, default = 0 },
+        retry_delay = { type = "integer", minimum = 0, default = 1 },
+        buffer_duration = { type = "integer", minimum = 1, default = 60 },
+        inactive_timeout = { type = "integer", minimum = 1, default = 10 },
+        batch_max_size = { type = "integer", minimum = 1, default = 100 },
+    },
+    oneOf = {
+        { required = { "auth_config" } },
+        { required = { "auth_file" } },
+    },
+}
+
+
+-- remove stale objects from the memory after timer expires
+local function remove_stale_objects(premature)
+    if premature then
+        return
+    end
+
+    for key, batch in ipairs(buffers) do
+        if #batch.entry_buffer.entries == 0 and #batch.batch_to_process == 0 then
+            core.log.warn("removing batch processor stale object, route id:", tostring(key))
+            buffers[key] = nil
+        end
+    end
+
+    stale_timer_running = false
+end
+
+
+local function send_to_google(oauth, entries)
+    local http_new = http.new()
+    local res, err = http_new:request_uri(oauth.entries_uri, {
+        ssl_verify = false,
+        method = "POST",
+        body = core.json.encode({
+            entries = entries,
+            partialSuccess = false,
+        }),
+        headers = {
+            ["Content-Type"] = "application/json",
+            ["Authorization"] = "Bearer " .. oauth:get_access_token(),
+        },
+    })
+
+    if err then
+        return nil, "failed to write log to google, ", err
+    end
+
+    if res.status ~= 200 then
+        return nil, res.body
+    end
+
+    return res.body
+end
+
+
+local function get_auth_config(config)
+    local err
+    local auth_config = {}
+    if config.auth_config then
+        auth_config = config.auth_config
+    end
+
+    if config.auth_file then
+        local file_content
+        file_content, err = core.utils.get_file(config.auth_file)
+        if file_content then
+            auth_config = core.json.decode(file_content)
+        end
+    end
+
+    if err then
+        return nil, err
+    end
+
+    return auth_config
+end
+
+
+local function get_logger_buffer(conf, ctx)
+    local oauth_client = google_oauth:new(auth_config_cache)
+
+    local process = function(entries)
+        return send_to_google(oauth_client, entries)
+    end
+
+    local config = {
+        name = conf.name or plugin_name,
+        retry_delay = conf.retry_delay,
+        batch_max_size = conf.batch_max_size,
+        max_retry_count = conf.max_retry_count,
+        buffer_duration = conf.buffer_duration,
+        inactive_timeout = conf.inactive_timeout,
+        route_id = ctx.var.route_id,
+        server_addr = ctx.var.server_addr,
+    }
+
+    local buffer, err = batch_processor:new(process, config)
+
+    if not buffer then
+        return nil, "error when creating the batch processor: " .. err
+    end
+
+    return buffer
+end
+
+
+local function get_utc_timestamp()
+    ngx_update_time()
+    local now = tostring(ngx_now())

Review comment:
       We can use math.ceil to get the second & millisecond?

##########
File path: apisix/plugins/google-logging.lua
##########
@@ -0,0 +1,298 @@
+--
+-- 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.
+--
+
+local core = require("apisix.core")
+local ngx = ngx
+local tostring = tostring
+local ipairs = ipairs
+local sub_str = string.sub
+local os_date = os.date
+
+local ngx_now = ngx.now
+local ngx_timer_at = ngx.timer.at
+local ngx_update_time = ngx.update_time
+
+local http = require("resty.http")
+local log_util = require("apisix.utils.log-util")
+local batch_processor = require("apisix.utils.batch-processor")
+local google_oauth = require("apisix.plugins.google-logging.oauth")
+
+
+local buffers = {}
+local auth_config_cache
+local stale_timer_running
+
+
+local plugin_name = "google-logging"
+local schema = {
+    type = "object",
+    properties = {
+        auth_config = {
+            type = "object",
+            properties = {
+                private_key = { type = "string" },
+                project_id = { type = "string" },
+                token_uri = {
+                    type = "string",
+                    default = "https://oauth2.googleapis.com/token"
+                },
+                -- https://developers.google.com/identity/protocols/oauth2/scopes#logging
+                scopes = {
+                    type = "array",
+                    items = {
+                        description = "Google OAuth2 Authorization Scopes",
+                        type = "string",
+                    },
+                    minItems = 1,
+                    uniqueItems = true,
+                    default = {
+                        "https://www.googleapis.com/auth/logging.read",
+                        "https://www.googleapis.com/auth/logging.write",
+                        "https://www.googleapis.com/auth/logging.admin",
+                        "https://www.googleapis.com/auth/cloud-platform"
+                    }
+                },
+                entries_uri = {
+                    type = "string",
+                    default = "https://logging.googleapis.com/v2/entries:write"
+                },
+            },
+            required = { "private_key", "project_id", "token_uri" }
+        },
+        auth_file = { type = "string" },
+        -- https://cloud.google.com/logging/docs/reference/v2/rest/v2/MonitoredResource
+        resource = {
+            type = "object",
+            properties = {
+                type = { type = "string" },
+                labels = { type = "object" }
+            },
+            default = {
+                type = "global"
+            },
+            required = { "type" }
+        },
+        -- https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry
+        log_id = { type = "string", default = "apisix.apache.org%2Flogs" },
+        max_retry_count = { type = "integer", minimum = 0, default = 0 },
+        retry_delay = { type = "integer", minimum = 0, default = 1 },
+        buffer_duration = { type = "integer", minimum = 1, default = 60 },
+        inactive_timeout = { type = "integer", minimum = 1, default = 10 },
+        batch_max_size = { type = "integer", minimum = 1, default = 100 },
+    },
+    oneOf = {
+        { required = { "auth_config" } },
+        { required = { "auth_file" } },
+    },
+}
+
+
+-- remove stale objects from the memory after timer expires
+local function remove_stale_objects(premature)
+    if premature then
+        return
+    end
+
+    for key, batch in ipairs(buffers) do
+        if #batch.entry_buffer.entries == 0 and #batch.batch_to_process == 0 then
+            core.log.warn("removing batch processor stale object, route id:", tostring(key))
+            buffers[key] = nil
+        end
+    end
+
+    stale_timer_running = false
+end
+
+
+local function send_to_google(oauth, entries)
+    local http_new = http.new()
+    local res, err = http_new:request_uri(oauth.entries_uri, {
+        ssl_verify = false,
+        method = "POST",
+        body = core.json.encode({
+            entries = entries,
+            partialSuccess = false,
+        }),
+        headers = {
+            ["Content-Type"] = "application/json",
+            ["Authorization"] = "Bearer " .. oauth:get_access_token(),
+        },
+    })
+
+    if err then
+        return nil, "failed to write log to google, ", err

Review comment:
       ```suggestion
           return nil, "failed to write log to google: " .. err
   ```

##########
File path: apisix/plugins/google-logging/oauth.lua
##########
@@ -0,0 +1,128 @@
+--
+-- 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.
+--
+
+local core = require("apisix.core")
+local type = type
+local setmetatable = setmetatable
+
+local ngx_update_time = ngx.update_time
+local ngx_time = ngx.time
+local ngx_encode_args = ngx.encode_args
+
+local http = require("resty.http")
+local jwt = require("resty.jwt")
+
+
+local function get_timestamp()
+    ngx_update_time()
+    return ngx_time()
+end
+
+
+local _M = {}
+
+
+function _M:get_access_token()
+    if not self.access_token or get_timestamp() > self.access_token_expire_time - 60 then
+        self:refresh_access_token()
+    end
+    return self.access_token
+end
+
+
+function _M:refresh_access_token()
+    local http_new = http.new()
+    local res, err = http_new:request_uri(self.token_uri, {
+        ssl_verify = false,
+        method = "POST",
+        body = ngx_encode_args({
+            grant_type = "urn:ietf:params:oauth:grant-type:jwt-bearer",
+            assertion = self:generate_jwt_token()
+        }),
+        headers = {
+            ["Content-Type"] = "application/x-www-form-urlencoded",
+        },
+    })
+
+    if not res then
+        core.log.error("failed to refresh google oauth access token, ", err)
+        return
+    end
+
+    if res.status ~= 200 then
+        core.log.error("failed to refresh google oauth access token: ", res.body)
+        return
+    end
+
+    res = core.json.decode(res.body)

Review comment:
       Need to check the decoded result.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org