Skip to content
This repository has been archived by the owner on Jan 7, 2018. It is now read-only.

Commit

Permalink
Some initial experiments getting logging working under the Lua branch.
Browse files Browse the repository at this point in the history
We're using lua-resty-logger-socket to push log data off to a Heka
server. Heka then plays the role of buffering and performing bulk
indexing against ElasticSearch.

This still needs some cleanup, but the overall strategy should hopefully
be a lot more straightforward than the previous strategy that involved
aggregating log data from multiple sources. Heka also performs buffering
and retries on disk, so this should also prevent memory from ballooning
up uncontrollably if ElasticSearch happends to temporarily go down.
  • Loading branch information
GUI committed May 25, 2015
1 parent 77a5ab1 commit 04e5673
Show file tree
Hide file tree
Showing 30 changed files with 15,509 additions and 204 deletions.
28 changes: 28 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ LUA_RESTY_HTTP_DIGEST:=md5
LUA_RESTY_HTTP_CHECKSUM:=bf489d545d99c11f8deef769cfd5fec2
LUA_RESTY_HTTP_URL:=https://github.com/pintsized/lua-resty-http/archive/v$(LUA_RESTY_HTTP_VERSION).tar.gz

LUA_RESTY_LOGGER_SOCKET_VERSION:=89864590fea7273bff37925d11e7fc4239bb2f8c
LUA_RESTY_LOGGER_SOCKET:=lua-resty-logger-socket-$(LUA_RESTY_LOGGER_SOCKET_VERSION)
LUA_RESTY_LOGGER_SOCKET_DIGEST:=md5
LUA_RESTY_LOGGER_SOCKET_CHECKSUM:=847c220a6d93262fc001e9761de9bcf0
LUA_RESTY_LOGGER_SOCKET_URL:=https://github.com/cloudflare/lua-resty-logger-socket/archive/$(LUA_RESTY_LOGGER_SOCKET_VERSION).tar.gz

LUA_RESTY_SHCACHE_VERSION:=fb2e275c2cdca08eaa34a7b73375e41ac3eff200
LUA_RESTY_SHCACHE:=lua-resty-shcache-$(LUA_RESTY_SHCACHE_VERSION)
LUA_RESTY_SHCACHE_DIGEST:=md5
Expand Down Expand Up @@ -235,6 +241,16 @@ deps/$(LUA_RESTY_HTTP): deps/$(LUA_RESTY_HTTP).tar.gz
tar --strip-components 1 -C $@ -xf $<
touch $@

# lua-resty-logger-socket
deps/$(LUA_RESTY_LOGGER_SOCKET).tar.gz: | deps
curl -L -o $@ $(LUA_RESTY_LOGGER_SOCKET_URL)

deps/$(LUA_RESTY_LOGGER_SOCKET): deps/$(LUA_RESTY_LOGGER_SOCKET).tar.gz
openssl $(LUA_RESTY_LOGGER_SOCKET_DIGEST) $< | grep $(LUA_RESTY_LOGGER_SOCKET_CHECKSUM) || (echo "checksum mismatch $<" && exit 1)
mkdir -p $@
tar --strip-components 1 -C $@ -xf $<
touch $@

# lua-resty-shcache
deps/$(LUA_RESTY_SHCACHE).tar.gz: | deps
curl -L -o $@ $(LUA_RESTY_SHCACHE_URL)
Expand Down Expand Up @@ -410,6 +426,8 @@ INSPECT:=inspect
INSPECT_VERSION:=3.0-1
LUA_CMSGPACK:=lua-cmsgpack
LUA_CMSGPACK_VERSION:=0.3-2
LUAUTF8:=luautf8
LUAUTF8_VERSION:=0.1.0-1
LYAML:=lyaml
LYAML_VERSION:=5.1.4-1
PENLIGHT:=penlight
Expand All @@ -425,6 +443,10 @@ vendor/lib/luarocks/rocks/$(LUA_CMSGPACK)/$(LUA_CMSGPACK_VERSION): deps/$(LUAROC
$(PREFIX)/embedded/bin/luarocks --tree=vendor install $(LUA_CMSGPACK) $(LUA_CMSGPACK_VERSION)
touch $@

vendor/lib/luarocks/rocks/$(LUAUTF8)/$(LUAUTF8_VERSION): deps/$(LUAROCKS)/.installed | vendor
$(PREFIX)/embedded/bin/luarocks --tree=vendor install $(LUAUTF8) $(LUAUTF8_VERSION)
touch $@

#vendor/lib/luarocks/rocks/$(LUA_LIBCIDR_FFI)/$(LUA_LIBCIDR_FFI_VERSION): deps/$(LUAROCKS)/.installed | vendor
# $(PREFIX)/embedded/bin/luarocks --tree=vendor install $(LUA_LIBCIDR_FFI) $(LUA_LIBCIDR_FFI_VERSION)
# touch $@
Expand All @@ -445,6 +467,10 @@ vendor/share/lua/5.1/resty/http.lua: deps/$(LUA_RESTY_HTTP) | vendor
rsync -a deps/$(LUA_RESTY_HTTP)/lib/resty/ vendor/share/lua/5.1/resty/
touch $@

vendor/share/lua/5.1/resty/logger/socket.lua: deps/$(LUA_RESTY_LOGGER_SOCKET) | vendor
rsync -a deps/$(LUA_RESTY_LOGGER_SOCKET)/lib/resty/ vendor/share/lua/5.1/resty/
touch $@

vendor/share/lua/5.1/shcache.lua: deps/$(LUA_RESTY_SHCACHE) | vendor
rsync -a deps/$(LUA_RESTY_SHCACHE)/*.lua vendor/share/lua/5.1/
touch $@
Expand All @@ -456,11 +482,13 @@ vendor/share/lua/5.1/lustache.lua: deps/$(LUSTACHE) | vendor
install_app_dependencies: \
vendor/lib/luarocks/rocks/$(INSPECT)/$(INSPECT_VERSION) \
vendor/lib/luarocks/rocks/$(LUA_CMSGPACK)/$(LUA_CMSGPACK_VERSION) \
vendor/lib/luarocks/rocks/$(LUAUTF8)/$(LUAUTF8_VERSION) \
vendor/lib/luarocks/rocks/$(LYAML)/$(LYAML_VERSION) \
vendor/lib/luarocks/rocks/$(PENLIGHT)/$(PENLIGHT_VERSION) \
vendor/lib/luarocks/rocks/$(STDLIB)/$(STDLIB_VERSION) \
vendor/share/lua/5.1/lustache.lua \
vendor/share/lua/5.1/resty/http.lua \
vendor/share/lua/5.1/resty/logger/socket.lua \
vendor/share/lua/5.1/shcache.lua

install: all install_dependencies install_app_dependencies
7 changes: 7 additions & 0 deletions conf/access.lua
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
local start_time = ngx.now()
local api_key_validator = require "api_key_validator"
local api_matcher = require "api_matcher"
local api_settings = require "api_settings"
Expand All @@ -9,6 +10,7 @@ local referer_validator = require "referer_validator"
local rewrite_request = require "rewrite_request"
local role_validator = require "role_validator"
local user_settings = require "user_settings"
local utils = require "utils"

local ngx_var = ngx.var

Expand Down Expand Up @@ -121,4 +123,9 @@ if err then
return error_handler(err, settings)
end

-- Store the settings for use by the header_filter.
ngx.ctx.settings = settings

-- Compute how much time we spent in Lua processing during this phase of the
-- request.
utils.overhead_timer(start_time)
8 changes: 8 additions & 0 deletions conf/api_key_validator.lua
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ local function resolve_api_key()
end
end

-- Store the api key for logging.
ngx.ctx.api_key = api_key

return api_key
end

Expand All @@ -47,6 +50,11 @@ return function(settings)
-- doesn't contain it directly, to save memory storage in the lookup table).
user["api_key"] = api_key

-- Store user details for logging.
ngx.ctx.user_id = user["id"]
ngx.ctx.user_email = user["email"]
ngx.ctx.user_registration_source = user["registration_source"]

-- Check to make sure the user isn't disabled.
if user["disabled_at"] then
return nil, "api_key_disabled"
Expand Down
146 changes: 146 additions & 0 deletions conf/elasticsearch_setup.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
local _M = {}

local cjson = require "cjson"
local http = require "resty.http"
local inspect = require "inspect"
local lock = require "resty.lock"

local lock = lock:new("my_locks", {
["timeout"] = 0,
})

local delay = 3600 -- in seconds
local new_timer = ngx.timer.at

local elasticsearch_host = config["elasticsearch"]["hosts"][1]

local function wait_for_elasticsearch()
local httpc = http.new()
local elasticsearch_alive = false
local wait_time = 0
local sleep_time = 0.5
local max_time = 60
repeat
local res, err = httpc:request_uri(elasticsearch_host .. "/_cluster/health")
if not err and res.body then
local elasticsearch_health = cjson.decode(res.body)
if elasticsearch_health["status"] == "yellow" or elasticsearch_health["status"] == "green" then
elasticsearch_alive = true
end
end

if not elasticsearch_alive then
ngx.sleep(sleep_time)
wait_time = wait_time + sleep_time
end
until elasticsearch_alive or wait_time > max_time
end

local function create_templates()
-- Template creation only needs to be run once on startup or reload.
local created = ngx.shared.config:get("elasticsearch_templates_created")
if created then return end

if elasticsearch_templates then
local httpc = http.new()
for _, template in ipairs(elasticsearch_templates) do
local res, err = httpc:request_uri(elasticsearch_host .. "/_template/" .. template["id"], {
method = "PUT",
body = cjson.encode(template["template"]),
})
end
end

ngx.shared.config:set("elasticsearch_templates_created", true)
end

local function create_aliases()
local today = os.date("%Y-%m", ngx.time())
local tomorrow = os.date("%Y-%m", ngx.time() + 86400)

local aliases = {
{
alias = "api-umbrella-logs-" .. today,
index = "api-umbrella-logs-" .. config["log_template_version"] .. "-" .. today,
},
{
alias = "api-umbrella-logs-write-" .. today,
index = "api-umbrella-logs-" .. config["log_template_version"] .. "-" .. today,
},
}

-- Create the aliases needed for the next day if we're at the end of the
-- month.
if tomorrow ~= today then
table.insert(aliases, {
alias = "api-umbrella-logs-" .. tomorrow,
index = "api-umbrella-logs-" .. config["log_template_version"] .. "-" .. tomorrow,
})
table.insert({
alias = "api-umbrella-logs-write-" .. tomorrow,
index = "api-umbrella-logs-" .. config["log_template_version"] .. "-" .. tomorrow,
})
end

local httpc = http.new()
for _, alias in ipairs(aliases) do
-- Make sure the index exists.
local res, err = httpc:request_uri(elasticsearch_host .. "/" .. alias["index"], {
method = "PUT",
})

-- Create the alias for the index.
local res, err = httpc:request_uri(elasticsearch_host .. "/" .. alias["index"] .. "/_alias/" .. alias["alias"], {
method = "PUT",
})
end
end

local function do_check()
local elapsed, err = lock:lock("elasticsearch_index_setup")
if err then
return
end

wait_for_elasticsearch()
create_templates()
create_aliases()

local ok, err = lock:unlock()
if not ok then
ngx.log(ngx.ERR, "failed to unlock: ", err)
end
end

local function check(premature)
if premature then
return
end

local ok, err = pcall(do_check)
if not ok then
ngx.log(ngx.ERR, "failed to run api load cycle: ", err)
end

-- We keep running this task so that we can ensure the indexes and aliases
-- always get created for the following day as we approach the end of the
-- month.
local ok, err = new_timer(delay, check)
if not ok then
if err ~= "process exiting" then
ngx.log(ngx.ERR, "failed to create timer: ", err)
end

return
end
end

function _M.spawn()
local ok, err = new_timer(0, check)
if not ok then
log(ERR, "failed to create timer: ", err)
return
end
end

return _M
3 changes: 3 additions & 0 deletions conf/error_handler.lua
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ local function render_template(template, data, format, strip_whitespace)
end

return function(err, settings, extra_data)
-- Store the gatekeeper rejection code for logging.
ngx.ctx.gatekeeper_denied_code = err

if not settings then
settings = config["apiSettings"]
end
Expand Down
6 changes: 6 additions & 0 deletions conf/header_filter.lua
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
local start_time = ngx.now()
local rewrite_response = require "rewrite_response"
local utils = require "utils"

local settings = ngx.ctx.settings

Expand All @@ -7,3 +9,7 @@ local err = rewrite_response(settings)
if err then
return error_handler(err, settings)
end

-- Compute how much time we spent in Lua processing during this phase of the
-- request.
utils.overhead_timer(start_time)
28 changes: 25 additions & 3 deletions conf/health.lua
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
local cjson = require "cjson"
local http = require "resty.http"
local inspect = require "inspect"

local response = {
Expand All @@ -7,27 +8,48 @@ local response = {
apis_config = "red",
api_users = "red",
analytics_db = "red",
analytics_db_setup = "red",
},
}

-- Check to see if the APIs have been loaded.
if ngx.shared.apis:get("last_fetched_at") then
response["details"]["apis_config"] = "green"
end

-- Check to see if the users have been loaded.
if ngx.shared.api_users:get("last_fetched_at") then
response["details"]["api_users"] = "green"
end

local http = require "resty.http"
-- Check the health of the ElasticSearch cluster
local httpc = http.new()

local res, err = httpc:request_uri(config["elasticsearch"]["hosts"][1] .. "/_cluster/health")
if not err and res.body then
local elasticsearch_health = cjson.decode(res.body)
response["details"]["analytics_db"] = elasticsearch_health["status"]
end

if response["details"]["apis_config"] == "green" and response["details"]["api_users"] == "green" and response["details"]["analytics_db"] == "green" then
-- Check to see if the ElasticSearch index aliases have been setup.
local today = os.date("%Y-%m", ngx.time())
local alias = "api-umbrella-logs-" .. today
local index = "api-umbrella-logs-" .. config["log_template_version"] .. "-" .. today
local res, err = httpc:request_uri(config["elasticsearch"]["hosts"][1] .. "/" .. index .. "/_alias/" .. alias)
if not err and res.body then
local elasticsearch_alias = cjson.decode(res.body)
if not elasticsearch_alias["error"] then
response["details"]["analytics_db_setup"] = "green"
end
end

-- If everything looks good on the components, then mark our overall status a green.
--
-- Note: We accept ElasticSearch being in yellow status as long as the aliases
-- are setup, since on very first alias creation (but prior to indexing any
-- content), ElasticSearch seems to get stuck in the yellow status, even though
-- everything appears operational (but then it becomes green once content
-- starts indexing).
if response["details"]["apis_config"] == "green" and response["details"]["api_users"] == "green" and (response["details"]["analytics_db"] == "yellow" or response["details"]["analytics_db"] == "green") and response["details"]["analytics_db_setup"] == "green" then
response["status"] = "green"
elseif response["details"]["apis_config"] == "green" and response["details"]["api_users"] == "green" then
response["status"] = "yellow"
Expand Down
4 changes: 4 additions & 0 deletions conf/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@ DEBUG = false
local load_config = require "load_config"
config = load_config.parse()

require "init_elasticsearch_templates_data"
require "init_user_agent_parser_data"

ngx.shared.apis:set("config_id", config["config_id"])
ngx.shared.apis:set("upstreams_inited", false)
ngx.shared.apis:delete("nginx_reloading_guard")
ngx.shared.apis:delete("version")
ngx.shared.apis:delete("last_fetched_at")
ngx.shared.config:set("elasticsearch_templates_created", false)
19 changes: 19 additions & 0 deletions conf/init_elasticsearch_templates_data.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
local cjson = require "cjson"

local path = "/vagrant/lua-integration/config/elasticsearch_templates.json"
local f, err = io.open(path, "rb")
if err then
ngx.log(ngx.ERR, "failed to open file: ", err)
else
local content = f:read("*all")
if content then
local ok, data = pcall(cjson.decode, content)
if ok then
elasticsearch_templates = data
else
ngx.log(ngx.ERR, "failed to parse json for ", path)
end
end

f:close()
end
Loading

0 comments on commit 04e5673

Please sign in to comment.