Skip to content

Commit 9047b51

Browse files
artur-barsegyanASverdlov
authored andcommitted
logging: impl custom log options per route
1 parent 3669ac5 commit 9047b51

File tree

5 files changed

+208
-10
lines changed

5 files changed

+208
-10
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
55
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
66

7+
## [Unreleased]
8+
- Added options `log_requests` and `log_errors` to `route()` method for customizing request log output and error log output respectively.
9+
710
## [1.0.3] - 2018-06-29
811
### Added
912
- Fixed eof detection

README.md

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,9 @@ To stop the server, use `httpd:stop()`.
8989
httpd = require('http.server').new(host, port[, { options } ])
9090
```
9191

92-
`host` and `port` must contain:
92+
`host` and `port` must contain:
9393
* For tcp socket: the host and port to bind to.
94-
* For unix socket: `unix/` and path to socket (for example `/tmp/http-server.sock`) to bind to.
94+
* For unix socket: `unix/` and path to socket (for example `/tmp/http-server.sock`) to bind to.
9595

9696
`options` may contain:
9797

@@ -110,8 +110,12 @@ httpd = require('http.server').new(host, port[, { options } ])
110110
type `text/html`, `text/plain` and `application/json`.
111111
* `display_errors` - return application errors and backtraces to the client
112112
(like PHP).
113-
* `log_errors` - log application errors using `log.error()`.
114-
* `log_requests` - log incoming requests.
113+
* `log_requests` - log incoming requests. This parameter can receive:
114+
- function value, supporting C-style formatting: log_requests(fmt, ...), where fmt is a format string and ... is Lua Varargs, holding arguments to be replaced in fmt.
115+
- boolean value, where `true` choose default `log.info` and `false` disable request logs at all.
116+
117+
By default uses `log.info` function for requests logging.
118+
* `log_errors` - same as the `log_requests` option but is used for error messages logging. By default uses `log.error()` function.
115119

116120
## Using routes
117121

@@ -158,6 +162,8 @@ The first argument for `route()` is a Lua table with one or more keys:
158162
* `path` - route path, as described earlier.
159163
* `name` - route name.
160164
* `method` - method on the route like `POST`, `GET`, `PUT`, `DELETE`
165+
* `log_requests` - option that overrides the server parameter of the same name but only for current route.
166+
* `log_errors` - option that overrides the server parameter of the same name but only for current route.
161167

162168
The second argument is the route handler to be used to produce
163169
a response to the request.

http/server.lua

Lines changed: 61 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -589,6 +589,50 @@ response_mt = {
589589
}
590590
}
591591

592+
local function is_function(obj)
593+
return type(obj) == 'function'
594+
end
595+
596+
local function get_request_logger(server_opts, route_opts)
597+
if route_opts and route_opts.endpoint.log_requests ~= nil then
598+
if is_function(route_opts.endpoint.log_requests) then
599+
return route_opts.endpoint.log_requests
600+
elseif route_opts.endpoint.log_requests == false then
601+
return log.debug
602+
end
603+
end
604+
605+
if server_opts.log_requests then
606+
if is_function(server_opts.log_requests) then
607+
return server_opts.log_requests
608+
end
609+
610+
return log.info
611+
end
612+
613+
return log.debug
614+
end
615+
616+
local function get_error_logger(server_opts, route_opts)
617+
if route_opts and route_opts.endpoint.log_errors ~= nil then
618+
if is_function(route_opts.endpoint.log_errors) then
619+
return route_opts.endpoint.log_errors
620+
elseif route_opts.endpoint.log_errors == false then
621+
return log.debug
622+
end
623+
end
624+
625+
if server_opts.log_errors then
626+
if is_function(server_opts.log_errors) then
627+
return server_opts.log_errors
628+
end
629+
630+
return log.error
631+
end
632+
633+
return log.debug
634+
end
635+
592636
local function handler(self, request)
593637
if self.hooks.before_dispatch ~= nil then
594638
self.hooks.before_dispatch(self, request)
@@ -601,7 +645,6 @@ local function handler(self, request)
601645
format = pformat
602646
end
603647

604-
605648
local r = self:match(request.method, request.path)
606649
if r == nil then
607650
return static_file(self, request, format)
@@ -685,9 +728,10 @@ local function process_client(self, s, peer)
685728
s:write('HTTP/1.0 100 Continue\r\n\r\n')
686729
end
687730

688-
local logreq = self.options.log_requests and log.info or log.debug
731+
local route = self:match(p.method, p.path)
732+
local logreq = get_request_logger(self.options, route)
689733
logreq("%s %s%s", p.method, p.path,
690-
p.query ~= "" and "?"..p.query or "")
734+
p.query ~= "" and "?"..p.query or "")
691735

692736
local res, reason = pcall(self.options.handler, self, p)
693737
p:read() -- skip remaining bytes of request body
@@ -697,9 +741,9 @@ local function process_client(self, s, peer)
697741
status = 500
698742
hdrs = {}
699743
local trace = debug.traceback()
700-
local logerror = self.options.log_errors and log.error or log.debug
744+
local logerror = get_error_logger(self.options, route)
701745
logerror('unhandled error: %s\n%s\nrequest:\n%s',
702-
tostring(reason), trace, tostring(p))
746+
tostring(reason), trace, tostring(p))
703747
if self.options.display_errors then
704748
body =
705749
"Unhandled error: " .. tostring(reason) .. "\n"
@@ -1099,6 +1143,18 @@ local function add_route(self, opts, sub)
10991143
opts.sub = sub
11001144
opts.url_for = url_for_route
11011145

1146+
if opts.log_requests ~= nil then
1147+
if type(opts.log_requests) ~= 'function' and type(opts.log_requests) ~= 'boolean' then
1148+
error("'log_requests' option should be a function or a boolean")
1149+
end
1150+
end
1151+
1152+
if opts.log_errors ~= nil then
1153+
if type(opts.log_errors) ~= 'function' and type(opts.log_errors) ~= 'boolean' then
1154+
error("'log_errors' option should be a function or a boolean")
1155+
end
1156+
end
1157+
11021158
if opts.name ~= nil then
11031159
if opts.name == 'current' then
11041160
error("Route can not have name 'current'")

test.sh

100644100755
File mode changed.

test/http.test.lua

Lines changed: 134 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ local yaml = require 'yaml'
1010
local urilib = require('uri')
1111

1212
local test = tap.test("http")
13-
test:plan(7)
13+
test:plan(8)
14+
1415
test:test("split_uri", function(test)
1516
test:plan(65)
1617
local function check(uri, rhs)
@@ -388,4 +389,136 @@ test:test("server requests", function(test)
388389
httpd:stop()
389390
end)
390391

392+
local log_queue = {}
393+
394+
local custom_logger = {
395+
debug = function() end,
396+
verbose = function(...)
397+
table.insert(log_queue, { log_lvl = 'verbose', })
398+
end,
399+
info = function(...)
400+
table.insert(log_queue, { log_lvl = 'info', msg = string.format(...)})
401+
end,
402+
warn = function(...)
403+
table.insert(log_queue, { log_lvl = 'warn', msg = string.format(...)})
404+
end,
405+
error = function(...)
406+
table.insert(log_queue, { log_lvl = 'error', msg = string.format(...)})
407+
end
408+
}
409+
410+
local function find_msg_in_log_queue(msg, strict)
411+
for _, log in ipairs(log_queue) do
412+
if not strict then
413+
if log.msg:match(msg) then
414+
return log
415+
end
416+
else
417+
if log.msg == msg then
418+
return log
419+
end
420+
end
421+
end
422+
end
423+
424+
local function clear_log_queue()
425+
log_queue = {}
426+
end
427+
428+
test:test("Custom log functions for route", function(test)
429+
test:plan(5)
430+
431+
test:test("Setting log option for server instance", function(test)
432+
test:plan(2)
433+
434+
local httpd = http_server.new("127.0.0.1", 12345, { log_requests = custom_logger.info, log_errors = custom_logger.error })
435+
httpd:route({ path='/' }, function(_) end)
436+
httpd:route({ path='/error' }, function(_) error('Some error...') end)
437+
httpd:start()
438+
439+
http_client.get("127.0.0.1:12345")
440+
test:is_deeply(find_msg_in_log_queue("GET /"), { log_lvl = 'info', msg = 'GET /' }, "Route should logging requests in custom logger if it's presents")
441+
clear_log_queue()
442+
443+
http_client.get("127.0.0.1:12345/error")
444+
test:ok(find_msg_in_log_queue("Some error...", false), "Route should logging error in custom logger if it's presents")
445+
clear_log_queue()
446+
447+
httpd:stop()
448+
end)
449+
450+
test:test("Setting log options for route", function(test)
451+
test:plan(8)
452+
local httpd = http_server.new("127.0.0.1", 12345, { log_requests = true, log_errors = false })
453+
local dummy_logger = function() end
454+
455+
local ok, err = pcall(httpd.route, httpd, { path = '/', log_requests = 3 })
456+
test:is(ok, false, "Route logger can't be a log_level digit")
457+
test:like(err, "'log_requests' option should be a function", "route() should return error message in case of incorrect logger option")
458+
459+
ok, err = pcall(httpd.route, httpd, { path = '/', log_requests = { info = dummy_logger } })
460+
test:is(ok, false, "Route logger can't be a table")
461+
test:like(err, "'log_requests' option should be a function", "route() should return error message in case of incorrect logger option")
462+
463+
local ok, err = pcall(httpd.route, httpd, { path = '/', log_errors = 3 })
464+
test:is(ok, false, "Route error logger can't be a log_level digit")
465+
test:like(err, "'log_errors' option should be a function", "route() should return error message in case of incorrect logger option")
466+
467+
ok, err = pcall(httpd.route, httpd, { path = '/', log_errors = { error = dummy_logger } })
468+
test:is(ok, false, "Route error logger can't be a table")
469+
test:like(err, "'log_errors' option should be a function", "route() should return error message in case of incorrect log_errors option")
470+
end)
471+
472+
test:test("Log output with custom loggers on route", function(test)
473+
test:plan(3)
474+
local httpd = http_server.new("127.0.0.1", 12345, { log_requests = true, log_errors = true })
475+
httpd:start()
476+
477+
httpd:route({ path = '/', log_requests = custom_logger.info, log_errors = custom_logger.error }, function(_) end)
478+
http_client.get("127.0.0.1:12345")
479+
test:is_deeply(find_msg_in_log_queue("GET /"), { log_lvl = 'info', msg = 'GET /' }, "Route should logging requests in custom logger if it's presents")
480+
clear_log_queue()
481+
482+
httpd.routes = {}
483+
httpd:route({ path = '/', log_requests = custom_logger.info, log_errors = custom_logger.error }, function(_)
484+
error("User business logic exception...")
485+
end)
486+
http_client.get("127.0.0.1:12345")
487+
test:is_deeply(find_msg_in_log_queue("GET /"), { log_lvl = 'info', msg = 'GET /' }, "Route should logging request and error in case of route exception")
488+
test:ok(find_msg_in_log_queue("User business logic exception...", false),
489+
"Route should logging error custom logger if it's presents in case of route exception")
490+
clear_log_queue()
491+
492+
httpd:stop()
493+
end)
494+
495+
test:test("Log route requests with turned off 'log_requests' option", function(test)
496+
test:plan(1)
497+
local httpd = http_server.new("127.0.0.1", 12345, { log_requests = false })
498+
httpd:start()
499+
500+
httpd:route({ path = '/', log_requests = custom_logger.info }, function(_) end)
501+
http_client.get("127.0.0.1:12345")
502+
test:is_deeply(find_msg_in_log_queue("GET /"), { log_lvl = 'info', msg = 'GET /' }, "Route can override logging requests if the http server have turned off 'log_requests' option")
503+
clear_log_queue()
504+
505+
httpd:stop()
506+
end)
507+
508+
test:test("Log route requests with turned off 'log_errors' option", function(test)
509+
test:plan(1)
510+
local httpd = http_server.new("127.0.0.1", 12345, { log_errors = false })
511+
httpd:start()
512+
513+
httpd:route({ path = '/', log_errors = custom_logger.error }, function(_)
514+
error("User business logic exception...")
515+
end)
516+
http_client.get("127.0.0.1:12345")
517+
test:ok(find_msg_in_log_queue("User business logic exception...", false), "Route can override logging requests if the http server have turned off 'log_errors' option")
518+
clear_log_queue()
519+
520+
httpd:stop()
521+
end)
522+
end)
523+
391524
os.exit(test:check() == true and 0 or 1)

0 commit comments

Comments
 (0)