-
Notifications
You must be signed in to change notification settings - Fork 2.6k
feat: add lago plugin #12196
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
feat: add lago plugin #12196
Changes from 30 commits
Commits
Show all changes
34 commits
Select commit
Hold shift + click to select a range
f38c53d
feat: add lago plugin
bzp2010 ee47149
fix: lint
bzp2010 7b56808
feat: add plugin list
bzp2010 d611db4
fix: lint
bzp2010 0991a9d
clean
bzp2010 636eaf3
add tests
bzp2010 fe08286
patch docker compose
bzp2010 475e71f
fix: write docker compose file
bzp2010 e839a85
use another api port
bzp2010 946c4c8
fix no front
bzp2010 d28677b
exclude pnpm lock yaml
bzp2010 65e6459
fix test
bzp2010 0cf47f9
fix license headers
bzp2010 cfb8693
fix exec
bzp2010 cf862d2
remove front container
bzp2010 aa8aa99
fix codespell
bzp2010 0a1f6cb
fix lint
bzp2010 68ddeaa
fix lint
bzp2010 199e580
skip ts and mts
bzp2010 93d9e96
fix exec stderr resp
bzp2010 49198d4
fix
bzp2010 dafb129
add docs
bzp2010 5e6b6cc
fix lint
bzp2010 36b5a12
fix lint
bzp2010 61a1ae3
fix lint
bzp2010 dda65cf
fix doc format
bzp2010 162098b
encrypt token
bzp2010 b8de637
fix comment
bzp2010 22e2fd7
fix comment
bzp2010 3258665
fix comment
bzp2010 e5df2bf
Apply suggestions from code review
bzp2010 73ce891
Merge branch 'master' into bzp/feat-lago-plugin
bzp2010 2e8b69e
Apply suggestions from code review
bzp2010 eb1bc3f
fix comments
bzp2010 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,229 @@ | ||
-- | ||
-- 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 type = type | ||
local pairs = pairs | ||
local math_random = math.random | ||
local ngx = ngx | ||
|
||
local http = require("resty.http") | ||
local bp_manager_mod = require("apisix.utils.batch-processor-manager") | ||
local core = require("apisix.core") | ||
local str_format = core.string.format | ||
|
||
local plugin_name = "lago" | ||
local batch_processor_manager = bp_manager_mod.new("lago logger") | ||
|
||
local schema = { | ||
type = "object", | ||
properties = { | ||
-- core configurations | ||
endpoint_addrs = { | ||
type = "array", | ||
minItems = 1, | ||
items = core.schema.uri_def, | ||
description = "Lago API address, like http://127.0.0.1:3000, " | ||
.. "it supports both self-hosted and cloud. If multiple endpoints are" | ||
.. " configured, the log will be pushed to a randomly determined" | ||
.. " endpoint from the list.", | ||
}, | ||
endpoint_uri = { | ||
type = "string", | ||
minLength = 1, | ||
default = "/api/v1/events/batch", | ||
description = "Lago API endpoint, it needs to be set to the batch send endpoint.", | ||
}, | ||
token = { | ||
type = "string", | ||
description = "Lago API key, create one for your organization on dashboard." | ||
}, | ||
event_transaction_id = { | ||
type = "string", | ||
description = "Event's transaction ID, it is used to identify and de-duplicate" | ||
.. " the event, it supports string templates containing APISIX and" | ||
.. " NGINX variables, like \"req_${request_id}\", which allows you" | ||
.. " to use values returned by upstream services or request-id" | ||
.. " plugin integration", | ||
}, | ||
event_subscription_id = { | ||
type = "string", | ||
description = "Event's subscription ID, which is automatically generated or" | ||
.. " specified by you when you assign the plan to the customer on" | ||
.. " Lago, used to associate API consumption to a customer subscription," | ||
.. " it supports string templates containing APISIX and NGINX variables," | ||
.. " like \"cus_${consumer_name}\", which allows you to use values" | ||
.. " returned by upstream services or APISIX consumer", | ||
}, | ||
event_code = { | ||
type = "string", | ||
description = "Lago billable metric's code for associating an event to a specified" | ||
.. "billable item", | ||
}, | ||
event_properties = { | ||
type = "object", | ||
patternProperties = { | ||
[".*"] = { | ||
type = "string", | ||
minLength = 1, | ||
}, | ||
}, | ||
description = "Event's properties, used to attach information to an event, this" | ||
.. " allows you to send certain information on a event to Lago, such" | ||
.. " as sending HTTP status to take a failed request off the bill, or" | ||
.. " sending the AI token consumption in the response body for accurate" | ||
.. " billing, its keys are fixed strings and its values can be string" | ||
.. " templates containing APISIX and NGINX variables, like \"${status}\"" | ||
}, | ||
|
||
-- connection layer configurations | ||
ssl_verify = {type = "boolean", default = true}, | ||
timeout = { | ||
type = "integer", | ||
minimum = 1, | ||
maximum = 60000, | ||
default = 3000, | ||
description = "timeout in milliseconds", | ||
}, | ||
keepalive = {type = "boolean", default = true}, | ||
keepalive_timeout = { | ||
type = "integer", | ||
minimum = 1000, | ||
default = 60000, | ||
description = "keepalive timeout in milliseconds", | ||
}, | ||
keepalive_pool = {type = "integer", minimum = 1, default = 5}, | ||
}, | ||
required = {"endpoint_addrs", "token", "event_transaction_id", "event_subscription_id", | ||
"event_code"}, | ||
encrypt_fields = {"token"}, | ||
} | ||
schema = batch_processor_manager:wrap_schema(schema) | ||
|
||
-- According to https://getlago.com/docs/api-reference/events/batch, the maximum batch size is 100, | ||
-- so we have to override the default batch size to make it work out of the box,the plugin does | ||
-- not set a maximum limit, so if Lago relaxes the limit, then user can modify it | ||
-- to a larger batch size | ||
-- This does not affect other plugins, schema is appended after deep copy | ||
schema.properties.batch_max_size.default = 100 | ||
|
||
|
||
local _M = { | ||
version = 0.1, | ||
priority = 415, | ||
name = plugin_name, | ||
schema = schema, | ||
} | ||
|
||
|
||
function _M.check_schema(conf, schema_type) | ||
local check = {"endpoint_addrs"} | ||
core.utils.check_https(check, conf, plugin_name) | ||
core.utils.check_tls_bool({"ssl_verify"}, conf, plugin_name) | ||
|
||
return core.schema.check(schema, conf) | ||
end | ||
|
||
|
||
local function send_http_data(conf, data) | ||
local body, err = core.json.encode(data) | ||
if not body then | ||
return false, str_format("failed to encode json: %s", err) | ||
end | ||
local params = { | ||
headers = { | ||
["Content-Type"] = "application/json", | ||
["Authorization"] = "Bearer " .. conf.token, | ||
}, | ||
keepalive = conf.keepalive, | ||
ssl_verify = conf.ssl_verify, | ||
method = "POST", | ||
body = body, | ||
} | ||
|
||
if conf.keepalive then | ||
params.keepalive_timeout = conf.keepalive_timeout | ||
params.keepalive_pool = conf.keepalive_pool | ||
end | ||
|
||
local httpc, err = http.new() | ||
if not httpc then | ||
return false, str_format("create http client error: %s", err) | ||
end | ||
httpc:set_timeout(conf.timeout) | ||
|
||
-- select an random endpoint and build URL | ||
local endpoint_url = conf.endpoint_addrs[math_random(#conf.endpoint_addrs)]..conf.endpoint_uri | ||
local res, err = httpc:request_uri(endpoint_url, params) | ||
if not res then | ||
return false, err | ||
end | ||
|
||
if res.status >= 300 then | ||
return false, str_format("lago api returned status: %d, body: %s", | ||
res.status, res.body or "") | ||
end | ||
|
||
return true | ||
end | ||
|
||
|
||
function _M.log(conf, ctx) | ||
-- build usage event | ||
local event_transaction_id, err = core.utils.resolve_var(conf.event_transaction_id, ctx.var) | ||
if err then | ||
core.log.error("failed to resolve event_transaction_id, event dropped: ", err) | ||
return | ||
end | ||
|
||
local event_subscription_id, err = core.utils.resolve_var(conf.event_subscription_id, ctx.var) | ||
if err then | ||
core.log.error("failed to resolve event_subscription_id, event dropped: ", err) | ||
return | ||
end | ||
|
||
local entry = { | ||
transaction_id = event_transaction_id, | ||
external_subscription_id = event_subscription_id, | ||
code = conf.event_code, | ||
timestamp = ngx.req.start_time(), | ||
} | ||
|
||
if conf.event_properties and type(conf.event_properties) == "table" then | ||
entry.properties = core.table.deepcopy(conf.event_properties) | ||
for key, value in pairs(entry.properties) do | ||
local new_val, err, n_resolved = core.utils.resolve_var(value, ctx.var) | ||
AlinsRan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if not err and n_resolved > 0 then | ||
entry.properties[key] = new_val | ||
end | ||
end | ||
end | ||
|
||
if batch_processor_manager:add_entry(conf, entry) then | ||
return | ||
end | ||
|
||
-- generate a function to be executed by the batch processor | ||
local func = function(entries) | ||
return send_http_data(conf, { | ||
events = entries, | ||
}) | ||
end | ||
|
||
batch_processor_manager:add_entry_to_new_processor(conf, entry, ctx, func) | ||
end | ||
|
||
|
||
return _M |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.