├── .github └── workflows │ └── semgrep.yml ├── .gitignore ├── README.md ├── lib └── resty │ └── logger │ └── socket.lua ├── t ├── bug.t ├── cert │ ├── test.crt │ └── test.key ├── flush.t ├── proto.t ├── sanity.t └── timeout.t └── valgrind.suppress /.github/workflows/semgrep.yml: -------------------------------------------------------------------------------- 1 | 2 | on: 3 | pull_request: {} 4 | workflow_dispatch: {} 5 | push: 6 | branches: 7 | - main 8 | - master 9 | name: Semgrep config 10 | jobs: 11 | semgrep: 12 | name: semgrep/ci 13 | runs-on: ubuntu-20.04 14 | env: 15 | SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }} 16 | SEMGREP_URL: https://cloudflare.semgrep.dev 17 | SEMGREP_APP_URL: https://cloudflare.semgrep.dev 18 | SEMGREP_VERSION_CHECK_URL: https://cloudflare.semgrep.dev/api/check-version 19 | container: 20 | image: returntocorp/semgrep 21 | steps: 22 | - uses: actions/checkout@v3 23 | - run: semgrep ci 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.swo 3 | *~ 4 | go 5 | t/servroot/ 6 | reindex 7 | *.t_ 8 | tags 9 | luacov.report.out 10 | luacov.stats.out 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Name 2 | ==== 3 | 4 | lua-resty-logger-socket - nonblocking remote access logging for Nginx 5 | 6 | Table of Contents 7 | ================= 8 | 9 | * [Name](#name) 10 | * [Status](#status) 11 | * [Description](#description) 12 | * [Synopsis](#synopsis) 13 | * [Methods](#methods) 14 | * [init](#init) 15 | * [initted](#initted) 16 | * [log](#log) 17 | * [flush](#flush) 18 | * [Installation](#installation) 19 | * [TODO](#todo) 20 | * [Authors](#authors) 21 | * [Copyright and License](#copyright-and-license) 22 | 23 | Status 24 | ====== 25 | 26 | This library is still experimental and under early development. 27 | 28 | Description 29 | =========== 30 | 31 | This lua library is a remote logging module for ngx_lua: 32 | 33 | http://wiki.nginx.org/HttpLuaModule 34 | 35 | This is aimed to replace Nginx's standard [ngx_http_log_module](http://nginx.org/en/docs/http/ngx_http_log_module.html) to push access logs to a remote server via an nonblocking socket. A common remote log server supporting sockets is [syslog-ng](http://www.balabit.com/network-security/syslog-ng). 36 | 37 | This Lua library takes advantage of ngx_lua's cosocket API, which ensures 38 | 100% nonblocking behavior. 39 | 40 | Synopsis 41 | ======== 42 | 43 | ```lua 44 | lua_package_path "/path/to/lua-resty-logger-socket/lib/?.lua;;"; 45 | 46 | server { 47 | location / { 48 | log_by_lua ' 49 | local logger = require "resty.logger.socket" 50 | if not logger.initted() then 51 | local ok, err = logger.init{ 52 | host = 'xxx', 53 | port = 1234, 54 | flush_limit = 1234, 55 | drop_limit = 5678, 56 | } 57 | if not ok then 58 | ngx.log(ngx.ERR, "failed to initialize the logger: ", 59 | err) 60 | return 61 | end 62 | end 63 | 64 | -- construct the custom access log message in 65 | -- the Lua variable "msg" 66 | 67 | local bytes, err = logger.log(msg) 68 | if err then 69 | ngx.log(ngx.ERR, "failed to log message: ", err) 70 | return 71 | end 72 | '; 73 | } 74 | } 75 | ``` 76 | 77 | [Back to TOC](#table-of-contents) 78 | 79 | Methods 80 | ======= 81 | 82 | This logger module is designed to be shared inside an Nginx worker process by all the requests. So currently only one remote log server is supported. We may support multiple log server sharding in the future. 83 | 84 | [Back to TOC](#table-of-contents) 85 | 86 | init 87 | ---- 88 | `syntax: ok, err = logger.init(user_config)` 89 | 90 | Initialize logger with user configurations. This logger must be initted before use. If you do not initialize the logger, you will get an error. 91 | 92 | Available user configurations are listed as follows: 93 | 94 | * `flush_limit` 95 | 96 | If the buffered messages' size plus the current message size reaches (`>=`) this limit (in bytes), the buffered log messages will be written to log server. Default to 4096 (4KB). 97 | 98 | * `drop_limit` 99 | 100 | If the buffered messages' size plus the current message size is larger than this limit (in bytes), the current log message will be dropped because of limited buffer size. Default drop_limit is 1048576 (1MB). 101 | 102 | * `timeout` 103 | 104 | Sets the timeout (in ms) protection for subsequent operations, including the *connect* method. Default value is 1000 (1 sec). 105 | 106 | * `host` 107 | 108 | log server host. 109 | 110 | * `port` 111 | 112 | log server port number. 113 | 114 | * `sock_type` 115 | 116 | IP protocol type to use for transport layer. Can be either "tcp" or "udp". Default is "tcp". 117 | 118 | * `path` 119 | 120 | If the log server uses a stream-typed unix domain socket, `path` is the socket file path. Note that host/port and path cannot both be empty. At least one must be supplied. 121 | 122 | * `max_retry_times` 123 | 124 | Max number of retry times after a connect to a log server failed or send log messages to a log server failed. 125 | 126 | * `retry_interval` 127 | 128 | The time delay (in ms) before retry to connect to a log server or retry to send log messages to a log server, default to 100 (0.1s). 129 | 130 | * `pool_size` 131 | 132 | Keepalive pool size used by sock:keepalive. Default to 10. 133 | 134 | * `max_buffer_reuse` 135 | 136 | Max number of reuse times of internal logging buffer before creating a new one (to prevent memory leak). 137 | 138 | * `periodic_flush` 139 | 140 | Periodic flush interval (in seconds). Set to `nil` to turn off this feature. 141 | 142 | * `ssl` 143 | 144 | Boolean, enable or disable connecting via SSL. Default to false. 145 | 146 | * `ssl_verify` 147 | 148 | Boolean, enable or disable verifying host and certificate match. Default to true. 149 | 150 | * `sni_host` 151 | 152 | Set the hostname to send in SNI and to use when verifying certificate match. 153 | 154 | [Back to TOC](#table-of-contents) 155 | 156 | initted 157 | -------- 158 | `syntax: initted = logger.initted()` 159 | 160 | Get a boolean value indicating whether this module has been initted (by calling the [init](#init) method). 161 | 162 | [Back to TOC](#table-of-contents) 163 | 164 | log 165 | --- 166 | `syntax: bytes, err = logger.log(msg)` 167 | 168 | Log a message. By default, the log message will be buffered in the logger module until `flush_limit` is reached in which case the logger will flush all the buffered messages to remote log server via a socket. 169 | `bytes` is the number of bytes that successfully buffered in the logger. If `bytes` is nil, `err` is a string describing what kind of error happens this time. If bytes is not nil, then `err` is a previous error message. `err` can be nil when `bytes` is not nil. 170 | 171 | [Back to TOC](#table-of-contents) 172 | 173 | flush 174 | ----- 175 | `syntax: bytes, err = logger.flush()` 176 | 177 | Flushes any buffered messages out to remote immediately. Usually you do not need 178 | to call this manually because flushing happens automatically when the buffer is full. 179 | 180 | [Back to TOC](#table-of-contents) 181 | 182 | Installation 183 | ============ 184 | 185 | You need to compile at least [ngx_lua 0.9.0](https://github.com/chaoslawful/lua-nginx-module/tags) with your Nginx. 186 | 187 | You need to configure 188 | the [lua_package_path](https://github.com/chaoslawful/lua-nginx-module#lua_package_path) directive to 189 | add the path of your `lua-resty-logger-socket` source tree to ngx_lua's Lua module search path, as in 190 | 191 | # nginx.conf 192 | http { 193 | lua_package_path "/path/to/lua-resty-logger-socket/lib/?.lua;;"; 194 | ... 195 | } 196 | 197 | and then load the library in Lua: 198 | 199 | local logger = require "resty.logger.socket" 200 | 201 | [Back to TOC](#table-of-contents) 202 | 203 | TODO 204 | ==== 205 | 206 | * Multiple log server sharding and/or failover support. 207 | * "match_similar" utf8 support test. 208 | 209 | [Back to TOC](#table-of-contents) 210 | 211 | Authors 212 | ======= 213 | 214 | Jiale Zhi , CloudFlare Inc. 215 | 216 | Yichun Zhang (agentzh) , CloudFlare Inc. 217 | 218 | [Back to TOC](#table-of-contents) 219 | 220 | Copyright and License 221 | ===================== 222 | 223 | This module is licensed under the BSD license. 224 | 225 | Copyright (C) 2013, by Jiale Zhi , CloudFlare Inc. 226 | 227 | Copyright (C) 2013, by Yichun Zhang , CloudFlare Inc. 228 | 229 | All rights reserved. 230 | 231 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 232 | 233 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 234 | 235 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 236 | 237 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 238 | 239 | [Back to TOC](#table-of-contents) 240 | 241 | -------------------------------------------------------------------------------- /lib/resty/logger/socket.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (C) 2013-2014 Jiale Zhi (calio), CloudFlare Inc. 2 | --require "luacov" 3 | 4 | local concat = table.concat 5 | local tcp = ngx.socket.tcp 6 | local udp = ngx.socket.udp 7 | local timer_at = ngx.timer.at 8 | local ngx_log = ngx.log 9 | local ngx_sleep = ngx.sleep 10 | local type = type 11 | local pairs = pairs 12 | local tostring = tostring 13 | local debug = ngx.config.debug 14 | 15 | local DEBUG = ngx.DEBUG 16 | local CRIT = ngx.CRIT 17 | 18 | local MAX_PORT = 65535 19 | 20 | 21 | -- table.new(narr, nrec) 22 | local succ, new_tab = pcall(require, "table.new") 23 | if not succ then 24 | new_tab = function () return {} end 25 | end 26 | 27 | local _M = new_tab(0, 5) 28 | 29 | local is_exiting 30 | 31 | if not ngx.config or not ngx.config.ngx_lua_version 32 | or ngx.config.ngx_lua_version < 9003 then 33 | 34 | is_exiting = function() return false end 35 | 36 | ngx_log(CRIT, "We strongly recommend you to update your ngx_lua module to " 37 | .. "0.9.3 or above. lua-resty-logger-socket will lose some log " 38 | .. "messages when Nginx reloads if it works with ngx_lua module " 39 | .. "below 0.9.3") 40 | else 41 | is_exiting = ngx.worker.exiting 42 | end 43 | 44 | 45 | _M._VERSION = '0.03' 46 | 47 | -- user config 48 | local flush_limit = 4096 -- 4KB 49 | local drop_limit = 1048576 -- 1MB 50 | local timeout = 1000 -- 1 sec 51 | local host 52 | local port 53 | local ssl = false 54 | local ssl_verify = true 55 | local sni_host 56 | local path 57 | local max_buffer_reuse = 10000 -- reuse buffer for at most 10000 58 | -- times 59 | local periodic_flush = nil 60 | local need_periodic_flush = nil 61 | local sock_type = 'tcp' 62 | 63 | -- internal variables 64 | local buffer_size = 0 65 | -- 2nd level buffer, it stores logs ready to be sent out 66 | local send_buffer = "" 67 | -- 1st level buffer, it stores incoming logs 68 | local log_buffer_data = new_tab(20000, 0) 69 | -- number of log lines in current 1st level buffer, starts from 0 70 | local log_buffer_index = 0 71 | 72 | local last_error 73 | 74 | local connecting 75 | local connected 76 | local exiting 77 | local retry_connect = 0 78 | local retry_send = 0 79 | local max_retry_times = 3 80 | local retry_interval = 100 -- 0.1s 81 | local pool_size = 10 82 | local flushing 83 | local logger_initted 84 | local counter = 0 85 | local ssl_session 86 | 87 | local function _write_error(msg) 88 | last_error = msg 89 | end 90 | 91 | local function _do_connect() 92 | local ok, err, sock 93 | 94 | if not connected then 95 | if (sock_type == 'udp') then 96 | sock, err = udp() 97 | else 98 | sock, err = tcp() 99 | end 100 | 101 | if not sock then 102 | _write_error(err) 103 | return nil, err 104 | end 105 | 106 | sock:settimeout(timeout) 107 | end 108 | 109 | -- "host"/"port" and "path" have already been checked in init() 110 | if host and port then 111 | if (sock_type == 'udp') then 112 | ok, err = sock:setpeername(host, port) 113 | else 114 | ok, err = sock:connect(host, port) 115 | end 116 | elseif path then 117 | ok, err = sock:connect("unix:" .. path) 118 | end 119 | 120 | if not ok then 121 | return nil, err 122 | end 123 | 124 | return sock 125 | end 126 | 127 | local function _do_handshake(sock) 128 | if not ssl then 129 | return sock 130 | end 131 | 132 | local session, err = sock:sslhandshake(ssl_session, sni_host or host, 133 | ssl_verify) 134 | if not session then 135 | return nil, err 136 | end 137 | 138 | ssl_session = session 139 | return sock 140 | end 141 | 142 | local function _connect() 143 | local err, sock 144 | 145 | if connecting then 146 | if debug then 147 | ngx_log(DEBUG, "previous connection not finished") 148 | end 149 | return nil, "previous connection not finished" 150 | end 151 | 152 | connected = false 153 | connecting = true 154 | 155 | retry_connect = 0 156 | 157 | while retry_connect <= max_retry_times do 158 | sock, err = _do_connect() 159 | 160 | if sock then 161 | sock, err = _do_handshake(sock) 162 | if sock then 163 | connected = true 164 | break 165 | end 166 | end 167 | 168 | if debug then 169 | ngx_log(DEBUG, "reconnect to the log server: ", err) 170 | end 171 | 172 | -- ngx.sleep time is in seconds 173 | if not exiting then 174 | ngx_sleep(retry_interval / 1000) 175 | end 176 | 177 | retry_connect = retry_connect + 1 178 | end 179 | 180 | connecting = false 181 | if not connected then 182 | return nil, "try to connect to the log server failed after " 183 | .. max_retry_times .. " retries: " .. err 184 | end 185 | 186 | return sock 187 | end 188 | 189 | local function _prepare_stream_buffer() 190 | local packet = concat(log_buffer_data, "", 1, log_buffer_index) 191 | send_buffer = send_buffer .. packet 192 | 193 | log_buffer_index = 0 194 | counter = counter + 1 195 | if counter > max_buffer_reuse then 196 | log_buffer_data = new_tab(20000, 0) 197 | counter = 0 198 | if debug then 199 | ngx_log(DEBUG, "log buffer reuse limit (" .. max_buffer_reuse 200 | .. ") reached, create a new \"log_buffer_data\"") 201 | end 202 | end 203 | end 204 | 205 | local function _do_flush() 206 | local ok, err, sock, bytes 207 | local packet = send_buffer 208 | 209 | sock, err = _connect() 210 | if not sock then 211 | return nil, err 212 | end 213 | 214 | bytes, err = sock:send(packet) 215 | if not bytes then 216 | -- "sock:send" always closes current connection on error 217 | return nil, err 218 | end 219 | 220 | if debug then 221 | ngx.update_time() 222 | ngx_log(DEBUG, ngx.now(), ":log flush:" .. bytes .. ":" .. packet) 223 | end 224 | 225 | if (sock_type ~= 'udp') then 226 | ok, err = sock:setkeepalive(0, pool_size) 227 | if not ok then 228 | return nil, err 229 | end 230 | end 231 | 232 | return bytes 233 | end 234 | 235 | local function _need_flush() 236 | if buffer_size > 0 then 237 | return true 238 | end 239 | 240 | return false 241 | end 242 | 243 | local function _flush_lock() 244 | if not flushing then 245 | if debug then 246 | ngx_log(DEBUG, "flush lock acquired") 247 | end 248 | flushing = true 249 | return true 250 | end 251 | return false 252 | end 253 | 254 | local function _flush_unlock() 255 | if debug then 256 | ngx_log(DEBUG, "flush lock released") 257 | end 258 | flushing = false 259 | end 260 | 261 | local function _flush() 262 | local err 263 | 264 | -- pre check 265 | if not _flush_lock() then 266 | if debug then 267 | ngx_log(DEBUG, "previous flush not finished") 268 | end 269 | -- do this later 270 | return true 271 | end 272 | 273 | if not _need_flush() then 274 | if debug then 275 | ngx_log(DEBUG, "no need to flush:", log_buffer_index) 276 | end 277 | _flush_unlock() 278 | return true 279 | end 280 | 281 | -- start flushing 282 | retry_send = 0 283 | if debug then 284 | ngx_log(DEBUG, "start flushing") 285 | end 286 | 287 | local bytes 288 | while retry_send <= max_retry_times do 289 | if log_buffer_index > 0 then 290 | _prepare_stream_buffer() 291 | end 292 | 293 | bytes, err = _do_flush() 294 | 295 | if bytes then 296 | break 297 | end 298 | 299 | if debug then 300 | ngx_log(DEBUG, "resend log messages to the log server: ", err) 301 | end 302 | 303 | -- ngx.sleep time is in seconds 304 | if not exiting then 305 | ngx_sleep(retry_interval / 1000) 306 | end 307 | 308 | retry_send = retry_send + 1 309 | end 310 | 311 | _flush_unlock() 312 | 313 | if not bytes then 314 | local err_msg = "try to send log messages to the log server " 315 | .. "failed after " .. max_retry_times .. " retries: " 316 | .. err 317 | _write_error(err_msg) 318 | return nil, err_msg 319 | else 320 | if debug then 321 | ngx_log(DEBUG, "send " .. bytes .. " bytes") 322 | end 323 | end 324 | 325 | buffer_size = buffer_size - #send_buffer 326 | send_buffer = "" 327 | 328 | return bytes 329 | end 330 | 331 | local function _periodic_flush(premature) 332 | if premature then 333 | exiting = true 334 | end 335 | 336 | if need_periodic_flush or exiting then 337 | -- no regular flush happened after periodic flush timer had been set 338 | if debug then 339 | ngx_log(DEBUG, "performing periodic flush") 340 | end 341 | _flush() 342 | else 343 | if debug then 344 | ngx_log(DEBUG, "no need to perform periodic flush: regular flush " 345 | .. "happened before") 346 | end 347 | need_periodic_flush = true 348 | end 349 | 350 | timer_at(periodic_flush, _periodic_flush) 351 | end 352 | 353 | local function _flush_buffer() 354 | local ok, err = timer_at(0, _flush) 355 | 356 | need_periodic_flush = false 357 | 358 | if not ok then 359 | _write_error(err) 360 | return nil, err 361 | end 362 | end 363 | 364 | local function _write_buffer(msg, len) 365 | log_buffer_index = log_buffer_index + 1 366 | log_buffer_data[log_buffer_index] = msg 367 | 368 | buffer_size = buffer_size + len 369 | 370 | 371 | return buffer_size 372 | end 373 | 374 | function _M.init(user_config) 375 | if (type(user_config) ~= "table") then 376 | return nil, "user_config must be a table" 377 | end 378 | 379 | for k, v in pairs(user_config) do 380 | if k == "host" then 381 | if type(v) ~= "string" then 382 | return nil, '"host" must be a string' 383 | end 384 | host = v 385 | elseif k == "port" then 386 | if type(v) ~= "number" then 387 | return nil, '"port" must be a number' 388 | end 389 | if v < 0 or v > MAX_PORT then 390 | return nil, ('"port" out of range 0~%s'):format(MAX_PORT) 391 | end 392 | port = v 393 | elseif k == "path" then 394 | if type(v) ~= "string" then 395 | return nil, '"path" must be a string' 396 | end 397 | path = v 398 | elseif k == "sock_type" then 399 | if type(v) ~= "string" then 400 | return nil, '"sock_type" must be a string' 401 | end 402 | if v ~= "tcp" and v ~= "udp" then 403 | return nil, '"sock_type" must be "tcp" or "udp"' 404 | end 405 | sock_type = v 406 | elseif k == "flush_limit" then 407 | if type(v) ~= "number" or v < 0 then 408 | return nil, 'invalid "flush_limit"' 409 | end 410 | flush_limit = v 411 | elseif k == "drop_limit" then 412 | if type(v) ~= "number" or v < 0 then 413 | return nil, 'invalid "drop_limit"' 414 | end 415 | drop_limit = v 416 | elseif k == "timeout" then 417 | if type(v) ~= "number" or v < 0 then 418 | return nil, 'invalid "timeout"' 419 | end 420 | timeout = v 421 | elseif k == "max_retry_times" then 422 | if type(v) ~= "number" or v < 0 then 423 | return nil, 'invalid "max_retry_times"' 424 | end 425 | max_retry_times = v 426 | elseif k == "retry_interval" then 427 | if type(v) ~= "number" or v < 0 then 428 | return nil, 'invalid "retry_interval"' 429 | end 430 | -- ngx.sleep time is in seconds 431 | retry_interval = v 432 | elseif k == "pool_size" then 433 | if type(v) ~= "number" or v < 0 then 434 | return nil, 'invalid "pool_size"' 435 | end 436 | pool_size = v 437 | elseif k == "max_buffer_reuse" then 438 | if type(v) ~= "number" or v < 0 then 439 | return nil, 'invalid "max_buffer_reuse"' 440 | end 441 | max_buffer_reuse = v 442 | elseif k == "periodic_flush" then 443 | if type(v) ~= "number" or v < 0 then 444 | return nil, 'invalid "periodic_flush"' 445 | end 446 | periodic_flush = v 447 | elseif k == "ssl" then 448 | if type(v) ~= "boolean" then 449 | return nil, '"ssl" must be a boolean value' 450 | end 451 | ssl = v 452 | elseif k == "ssl_verify" then 453 | if type(v) ~= "boolean" then 454 | return nil, '"ssl_verify" must be a boolean value' 455 | end 456 | ssl_verify = v 457 | elseif k == "sni_host" then 458 | if type(v) ~= "string" then 459 | return nil, '"sni_host" must be a string' 460 | end 461 | sni_host = v 462 | end 463 | end 464 | 465 | if not (host and port) and not path then 466 | return nil, "no logging server configured. \"host\"/\"port\" or " 467 | .. "\"path\" is required." 468 | end 469 | 470 | 471 | if (flush_limit >= drop_limit) then 472 | return nil, "\"flush_limit\" should be < \"drop_limit\"" 473 | end 474 | 475 | flushing = false 476 | exiting = false 477 | connecting = false 478 | 479 | connected = false 480 | retry_connect = 0 481 | retry_send = 0 482 | 483 | logger_initted = true 484 | 485 | if periodic_flush then 486 | if debug then 487 | ngx_log(DEBUG, "periodic flush enabled for every " 488 | .. periodic_flush .. " seconds") 489 | end 490 | need_periodic_flush = true 491 | timer_at(periodic_flush, _periodic_flush) 492 | end 493 | 494 | return logger_initted 495 | end 496 | 497 | function _M.log(msg) 498 | if not logger_initted then 499 | return nil, "not initialized" 500 | end 501 | 502 | local bytes 503 | 504 | if type(msg) ~= "string" then 505 | msg = tostring(msg) 506 | end 507 | 508 | local msg_len = #msg 509 | 510 | if (debug) then 511 | ngx.update_time() 512 | ngx_log(DEBUG, ngx.now(), ":log message length: " .. msg_len) 513 | end 514 | 515 | -- response of "_flush_buffer" is not checked, because it writes 516 | -- error buffer 517 | if (is_exiting()) then 518 | exiting = true 519 | _write_buffer(msg, msg_len) 520 | _flush_buffer() 521 | if (debug) then 522 | ngx_log(DEBUG, "Nginx worker is exiting") 523 | end 524 | bytes = 0 525 | elseif (msg_len + buffer_size < flush_limit) then 526 | _write_buffer(msg, msg_len) 527 | bytes = msg_len 528 | elseif (msg_len + buffer_size <= drop_limit) then 529 | _write_buffer(msg, msg_len) 530 | _flush_buffer() 531 | bytes = msg_len 532 | else 533 | _flush_buffer() 534 | if (debug) then 535 | ngx_log(DEBUG, "logger buffer is full, this log message will be " 536 | .. "dropped") 537 | end 538 | bytes = 0 539 | --- this log message doesn't fit in buffer, drop it 540 | end 541 | 542 | if last_error then 543 | local err = last_error 544 | last_error = nil 545 | return bytes, err 546 | end 547 | 548 | return bytes 549 | end 550 | 551 | function _M.initted() 552 | return logger_initted 553 | end 554 | 555 | _M.flush = _flush 556 | 557 | return _M 558 | 559 | -------------------------------------------------------------------------------- /t/bug.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et: 2 | 3 | BEGIN { 4 | if (!defined $ENV{LD_PRELOAD}) { 5 | $ENV{LD_PRELOAD} = ''; 6 | } 7 | 8 | if ($ENV{LD_PRELOAD} !~ /\bmockeagain\.so\b/) { 9 | $ENV{LD_PRELOAD} = "mockeagain.so $ENV{LD_PRELOAD}"; 10 | } 11 | 12 | $ENV{MOCKEAGAIN} = 'w'; 13 | 14 | $ENV{MOCKEAGAIN_VERBOSE} = 1; 15 | $ENV{TEST_NGINX_EVENT_TYPE} = 'poll'; 16 | $ENV{MOCKEAGAIN_WRITE_TIMEOUT_PATTERN} = 'hello'; 17 | $ENV{TEST_NGINX_POSTPONE_OUTPUT} = 1; 18 | } 19 | 20 | use Test::Nginx::Socket::Lua; 21 | use Cwd qw(cwd); 22 | 23 | repeat_each(2); 24 | 25 | plan tests => repeat_each() * (blocks() * 3 + 2); 26 | our $HtmlDir = html_dir; 27 | 28 | my $pwd = cwd(); 29 | 30 | our $HttpConfig = qq{ 31 | lua_package_path "$pwd/lib/?.lua;;"; 32 | lua_package_cpath "/usr/local/openresty-debug/lualib/?.so;/usr/local/openresty/lualib/?.so;;"; 33 | }; 34 | 35 | $ENV{TEST_NGINX_RESOLVER} = '8.8.8.8'; 36 | $ENV{TEST_NGINX_HTML_DIR} = $HtmlDir; 37 | 38 | no_long_string(); 39 | 40 | log_level('debug'); 41 | 42 | run_tests(); 43 | 44 | __DATA__ 45 | 46 | === TEST 1: small flush_limit, instant flush 47 | --- http_config eval: $::HttpConfig 48 | --- config 49 | log_subrequest on; 50 | location /log { 51 | content_by_lua 'ngx.print("foo")'; 52 | log_by_lua ' 53 | local logger = require "resty.logger.socket" 54 | if not logger.initted() then 55 | local ok, err = logger.init{ 56 | host = "127.0.0.1", 57 | port = 29999, 58 | flush_limit = 5, 59 | drop_limit = 11, 60 | pool_size = 5, 61 | retry_interval = 1, 62 | max_retry_times = 0, 63 | timeout = 10, 64 | } 65 | end 66 | 67 | local ok, err = logger.log(ngx.var.arg_log) 68 | '; 69 | 70 | } 71 | location /t { 72 | content_by_lua ' 73 | local res = ngx.location.capture("/log?log=helloworld") 74 | ngx.say(res.body) 75 | ngx.sleep(0.05) 76 | 77 | res = ngx.location.capture("/log?log=bb") 78 | ngx.say(res.body) 79 | ngx.sleep(0.05) 80 | 81 | res = ngx.location.capture("/log?log=bb") 82 | ngx.say(res.body) 83 | '; 84 | } 85 | --- request 86 | GET /t?a=1&b=2 87 | --- wait: 0.2 88 | --- tcp_listen: 29999 89 | --- tcp_reply: 90 | --- tcp_no_close 91 | --- grep_error_log chop 92 | resend log messages to the log server: timeout 93 | --- response_body 94 | foo 95 | foo 96 | foo 97 | --- grep_error_log_out 98 | resend log messages to the log server: timeout 99 | resend log messages to the log server: timeout 100 | resend log messages to the log server: timeout 101 | 102 | 103 | 104 | === TEST 2: insert new log messages to buffer in the middle of last send (it's difficult to control the time sequence here, so this is skipped now) 105 | --- http_config eval: $::HttpConfig 106 | --- config 107 | log_subrequest on; 108 | location /log { 109 | content_by_lua 'ngx.print("foo")'; 110 | log_by_lua ' 111 | local logger = require "resty.logger.socket" 112 | if not logger.initted() then 113 | local ok, err = logger.init{ 114 | host = "127.0.0.1", 115 | port = 29999, 116 | flush_limit = 6, 117 | drop_limit = 1000, 118 | pool_size = 5, 119 | retry_interval = 1, 120 | max_retry_times = 0, 121 | timeout = 10, 122 | } 123 | end 124 | 125 | local ok, err = logger.log(ngx.var.arg_log) 126 | '; 127 | 128 | } 129 | location /t { 130 | content_by_lua ' 131 | local res = ngx.location.capture("/log?log=123456") 132 | ngx.say(res.body) 133 | ngx.sleep(0.002) 134 | 135 | res = ngx.location.capture("/log?log=aaaa") 136 | ngx.say(res.body) 137 | ngx.sleep(0.001) 138 | 139 | res = ngx.location.capture("/log?log=bb") 140 | ngx.say(res.body) 141 | '; 142 | } 143 | --- request 144 | GET /t?a=1&b=2 145 | --- wait: 0.2 146 | --- tcp_listen: 29999 147 | --- tcp_reply: 148 | --- tcp_no_close 149 | --- tcp_query: 123456aaaabb 150 | --- tcp_query_len: 12 151 | --- response_body 152 | foo 153 | foo 154 | foo 155 | --- SKIP 156 | 157 | 158 | 159 | === TEST 3: Test deadlock (https://github.com/cloudflare/lua-resty-logger-socket/pull/5) 160 | --- http_config eval: $::HttpConfig 161 | --- config 162 | location /t { 163 | content_by_lua ' 164 | ngx.say("foo") 165 | 166 | local logger = require "resty.logger.socket" 167 | if not logger.initted() then 168 | local ok, err = logger.init{ 169 | host = "127.0.0.1", 170 | port = 29999, 171 | flush_limit = 0, 172 | drop_limit = 1000, 173 | pool_size = 5, 174 | retry_interval = 1, 175 | timeout = 100, 176 | } 177 | end 178 | local ok, err = logger.log("") 179 | if not ok then 180 | ngx.log(ngx.ERR, err) 181 | end 182 | 183 | ngx.sleep(0.05) 184 | 185 | local ok, err = logger.log(ngx.var.request_uri) 186 | if not ok then 187 | ngx.log(ngx.ERR, err) 188 | end 189 | '; 190 | } 191 | --- request 192 | GET /t?a=1&b=2 193 | --- wait: 0.1 194 | --- tcp_listen: 29999 195 | --- tcp_reply: 196 | --- no_error_log 197 | [error] 198 | --- tcp_query: /t?a=1&b=2 199 | --- tcp_query_len: 10 200 | --- response_body 201 | foo 202 | -------------------------------------------------------------------------------- /t/cert/test.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDgzCCAmugAwIBAgIJALyCyaxg9zbFMA0GCSqGSIb3DQEBBQUAMFgxCzAJBgNV 3 | BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX 4 | aWRnaXRzIFB0eSBMdGQxETAPBgNVBAMMCHRlc3QuY29tMB4XDTE1MDIwOTIwMDEy 5 | M1oXDTE2MDIwOTIwMDEyM1owWDELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUt 6 | U3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDERMA8GA1UE 7 | AwwIdGVzdC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDtWTKs 8 | uFmcJcwQizcCHQXWXZO62h1IOuuE2+lygpJTjHgVeFniH7hRiXWLviza0RdvBx1j 9 | eTy1/OVGPOZo/mFNVkAQ4h3YBNphQxwlXIj+tzLH6/OPozKj+JsKXJU9VrBSyw2W 10 | KR1G+F+9NmEuteH361YR3O1WUrWuihCgXGc3VXevEyS4rjvu4pV8x73LUQJ7Hphm 11 | /rm9mAuLSXIDIZMewgg5lQKbVTEYKJppdLv0GztLztXmuNzD7ohmbksHfzCwPJ7b 12 | d4kdDeivVj+mw//TShWgDGSs/VkUbGrLjJ6wJBM84jbnlJuMEQezlFsCQbgv+AK5 13 | ypR6ZpcPkhfeaimHAgMBAAGjUDBOMB0GA1UdDgQWBBT07/1maBvi4l1OsOtj/pow 14 | F91UhzAfBgNVHSMEGDAWgBT07/1maBvi4l1OsOtj/powF91UhzAMBgNVHRMEBTAD 15 | AQH/MA0GCSqGSIb3DQEBBQUAA4IBAQBSQKKiuE9zAb3QsCPAgqW+XmPVSoMcAdfV 16 | /cJCLkGWnEUsiCX+D7M4+BqDxj4tnWXfwUD8kk86w1KCMTCyVSZXT9ru99RFaPF/ 17 | /VGXXxXAQGcck3RqoURct4vcK1M5JJI7+89bu2gP9SbeFebPN+eaVaSdVyEAU3WU 18 | IA+2Oa1GDrVMG/ilJvG2g7ON0R21L5CggiPleYPEf9ACqN+QgCq3ms+6jwTs4L2n 19 | DPzRmgjvnEU8XOnt30SiEzWsBlOf54kIsyypBGEeHOqJhbIPpp2N7CDkz4Hzjh4S 20 | pwjA69Xy5W+MfiL/Swd+uzatmylAswM/Tuw4P7Levd7QdT8HLvt+ 21 | -----END CERTIFICATE----- 22 | -------------------------------------------------------------------------------- /t/cert/test.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDtWTKsuFmcJcwQ 3 | izcCHQXWXZO62h1IOuuE2+lygpJTjHgVeFniH7hRiXWLviza0RdvBx1jeTy1/OVG 4 | POZo/mFNVkAQ4h3YBNphQxwlXIj+tzLH6/OPozKj+JsKXJU9VrBSyw2WKR1G+F+9 5 | NmEuteH361YR3O1WUrWuihCgXGc3VXevEyS4rjvu4pV8x73LUQJ7Hphm/rm9mAuL 6 | SXIDIZMewgg5lQKbVTEYKJppdLv0GztLztXmuNzD7ohmbksHfzCwPJ7bd4kdDeiv 7 | Vj+mw//TShWgDGSs/VkUbGrLjJ6wJBM84jbnlJuMEQezlFsCQbgv+AK5ypR6ZpcP 8 | khfeaimHAgMBAAECggEACY3Dmmw5Py3OTwQrjKw6ZeySW70X3KZygICRnwhed3p6 9 | AvM7Ovzn4HBWCO/4wj4UYWW56x0gMnshh3hAFg3XQNOt9IAFj4Jyf+PBqEdXRFl/ 10 | c8itnySEj1EcfIhJgZSRuGNwApFZGlg5Ela0BrtPgzk317Q64XSm14W7MZlGllWI 11 | iZZEh1q1TTA1CfroLjK0XOQqK+EQwkNuKca4T8YytcJVG6eSJApoYf0pKNVgRTnW 12 | tFAa3TrzxqhEEfw4dlIce6kTrGrNxhb2QZ2OeoYzsO140bn+yXs80a8reIxSWpzK 13 | lag6rMkonKCmKi8cU1HQbKbLPskteXs+Fx+PzJKlMQKBgQD3Jn/GN/S4TRpEw6R7 14 | CgsVZ/rhBGaYB2lEyYOb72Ex7BQFFAiqDes9EEssvd+0baRnVxkd0GCGtbF0laor 15 | ogDTDLuYv8VCaBmWZAy8aFhZbT8ygLjAjhOU1p5PWXgngyj+cPo5K0Hj1W/xCDcv 16 | 0bmjhFguHlx1M6hhPO7ABYYjewKBgQD12NlsUdRttdIxEaV6KA2WKcvi2zIspUdi 17 | TBIaiq1P7a2KO2oDMStqlDvWSIvM2kyGHM6kWuAZWC5zSBndjHMH3+hXrHBEreVX 18 | 3dcvx2El/3O36zuGSEBMye/EIlSe323dPohGObgW1iSaLdw+8dP+TiaJV6coh03g 19 | MffgFTVeZQKBgQD1a196P9pcoQOywOu12jVDXmt7wlj2InXf/pMX508GubzvhgNM 20 | imHL00Ay/6ECk9WrYIvqVR9k5Ut/z5aZmVdkO8KVXejNln3CHzueY6dHtfoJdlT/ 21 | sJW1OKEffmKYKeAtOZbf6hqV2T49hMD4VTQYMbU2pqN9JnzPgig6ucGHvQKBgDpn 22 | TpeWBQIfLJTtnUMc2sVunnoBGrVSelfWvIJDqZKQYyawoKmrd6X1GxX33AZJYd6G 23 | X2zaDdzXfwlx+nsQT49yWM7jLzSRnyc1k1ZNZj3RptrtbhGjfmr/mg8dHL81UvM5 24 | VMRiF+KM8tsRw/3ME1GZnTwJb7OIGS8Gj91TDH+lAoGBAO/noqWfPpKlWB3gjj5q 25 | mUZXyHiqo7Rze5cLCif2CBt3GIhT/XS97kCJZLbWRUC/Hy7ncTtq/aATmqKkhr2U 26 | Uov2oz0B64/L89Ymy0KsAJczdoKWEd5A+Wg1uW+yju01Ds0sO3uW1HQJlqxPTTDE 27 | cMyz91ugbaoCZV877haZMklH 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /t/flush.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et: 2 | 3 | use Test::Nginx::Socket::Lua; 4 | use Cwd qw(cwd); 5 | 6 | repeat_each(2); 7 | 8 | plan tests => repeat_each() * (blocks() * 5); 9 | our $HtmlDir = html_dir; 10 | 11 | my $pwd = cwd(); 12 | 13 | our $HttpConfig = qq{ 14 | lua_package_path "$pwd/lib/?.lua;;"; 15 | lua_package_cpath "/usr/local/openresty-debug/lualib/?.so;/usr/local/openresty/lualib/?.so;;"; 16 | }; 17 | 18 | $ENV{TEST_NGINX_RESOLVER} = '8.8.8.8'; 19 | $ENV{TEST_NGINX_HTML_DIR} = $HtmlDir; 20 | 21 | no_long_string(); 22 | 23 | log_level('debug'); 24 | 25 | run_tests(); 26 | 27 | __DATA__ 28 | 29 | === TEST 1: flush manually 30 | --- http_config eval: $::HttpConfig 31 | --- config 32 | location /t { 33 | content_by_lua ' 34 | collectgarbage() -- to help leak testing 35 | 36 | local logger = require "resty.logger.socket" 37 | if not logger.initted() then 38 | local ok, err = logger.init{ 39 | host = "127.0.0.1", 40 | port = 29999, 41 | flush_limit = 100, 42 | } 43 | end 44 | 45 | local bytes, err = logger.log("abc") 46 | if err then 47 | ngx.log(ngx.ERR, err) 48 | end 49 | 50 | local bytes, err = logger.log("efg") 51 | if err then 52 | ngx.log(ngx.ERR, err) 53 | end 54 | 55 | local bytes, err = logger.flush() 56 | ngx.say(bytes) 57 | '; 58 | } 59 | --- request 60 | GET /t?a=1&b=2 61 | --- wait: 0.1 62 | --- tcp_listen: 29999 63 | --- tcp_reply: 64 | --- no_error_log 65 | [error] 66 | --- tcp_query: abcefg 67 | --- tcp_query_len: 6 68 | --- response_body 69 | 6 70 | 71 | -------------------------------------------------------------------------------- /t/proto.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et: 2 | 3 | use Test::Nginx::Socket::Lua; 4 | use Cwd qw(cwd); 5 | 6 | repeat_each(2); 7 | 8 | plan tests => repeat_each() * (blocks() * 3 + 2); 9 | our $HtmlDir = html_dir; 10 | 11 | our $pwd = cwd(); 12 | 13 | our $HttpConfig = qq{ 14 | lua_package_path "$pwd/lib/?.lua;;"; 15 | lua_package_cpath "/usr/local/openresty-debug/lualib/?.so;/usr/local/openresty/lualib/?.so;;"; 16 | }; 17 | 18 | $ENV{TEST_NGINX_RESOLVER} = '8.8.8.8'; 19 | $ENV{TEST_NGINX_HTML_DIR} = $HtmlDir; 20 | 21 | no_long_string(); 22 | 23 | log_level('debug'); 24 | 25 | run_tests(); 26 | 27 | __DATA__ 28 | 29 | === TEST 1: default sock_type (tcp) 30 | --- http_config eval: $::HttpConfig 31 | --- config 32 | location /t { 33 | content_by_lua 'ngx.say("foo")'; 34 | log_by_lua ' 35 | collectgarbage() -- to help leak testing 36 | 37 | local logger = require "resty.logger.socket" 38 | if not logger.initted() then 39 | local ok, err = logger.init{ 40 | host = "127.0.0.1", 41 | port = 29999, 42 | flush_limit = 1, 43 | } 44 | end 45 | 46 | local bytes, err = logger.log(ngx.var.request_uri) 47 | if err then 48 | ngx.log(ngx.ERR, err) 49 | end 50 | '; 51 | } 52 | --- request 53 | GET /t?a=1&b=2 54 | --- wait: 0.1 55 | --- tcp_listen: 29999 56 | --- tcp_reply: 57 | --- no_error_log 58 | [error] 59 | --- tcp_query: /t?a=1&b=2 60 | --- tcp_query_len: 10 61 | 62 | 63 | 64 | === TEST 2: set sock_type tcp 65 | --- http_config eval: $::HttpConfig 66 | --- config 67 | location /t { 68 | content_by_lua 'ngx.say("foo")'; 69 | log_by_lua ' 70 | collectgarbage() -- to help leak testing 71 | 72 | local logger = require "resty.logger.socket" 73 | if not logger.initted() then 74 | local ok, err = logger.init{ 75 | host = "127.0.0.1", 76 | port = 29999, 77 | sock_type = "tcp", 78 | flush_limit = 1, 79 | } 80 | 81 | if err then ngx.log(ngx.ERR, err) end 82 | end 83 | 84 | local bytes, err = logger.log(ngx.var.request_uri) 85 | if err then 86 | ngx.log(ngx.ERR, err) 87 | end 88 | '; 89 | } 90 | --- request 91 | GET /t?a=1&b=2 92 | --- wait: 0.1 93 | --- tcp_listen: 29999 94 | --- tcp_reply: 95 | --- no_error_log 96 | [error] 97 | --- tcp_query: /t?a=1&b=2 98 | --- tcp_query_len: 10 99 | 100 | 101 | 102 | === TEST 3: set sock_type udp 103 | --- http_config eval: $::HttpConfig 104 | --- config 105 | location /t { 106 | content_by_lua 'ngx.say("foo")'; 107 | log_by_lua ' 108 | collectgarbage() -- to help leak testing 109 | 110 | local logger = require "resty.logger.socket" 111 | if not logger.initted() then 112 | local ok, err = logger.init{ 113 | host = "127.0.0.1", 114 | port = 29999, 115 | sock_type = "udp", 116 | flush_limit = 1, 117 | } 118 | 119 | if err then ngx.log(ngx.ERR, err) end 120 | end 121 | 122 | local bytes, err = logger.log(ngx.var.request_uri) 123 | if err then 124 | ngx.log(ngx.ERR, err) 125 | end 126 | '; 127 | } 128 | --- request 129 | GET /t?a=1&b=2 130 | --- wait: 0.1 131 | --- udp_listen: 29999 132 | --- udp_reply: 133 | --- no_error_log 134 | [error] 135 | --- udp_query: /t?a=1&b=2 136 | 137 | -------------------------------------------------------------------------------- /t/sanity.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et: 2 | 3 | use Test::Nginx::Socket::Lua; 4 | use Cwd qw(cwd); 5 | 6 | repeat_each(2); 7 | 8 | plan tests => repeat_each() * (blocks() * 4 + 19); 9 | our $HtmlDir = html_dir; 10 | 11 | our $pwd = cwd(); 12 | 13 | our $HttpConfig = qq{ 14 | lua_package_path "$pwd/lib/?.lua;;"; 15 | lua_package_cpath "/usr/local/openresty-debug/lualib/?.so;/usr/local/openresty/lualib/?.so;;"; 16 | }; 17 | 18 | $ENV{TEST_NGINX_RESOLVER} = '8.8.8.8'; 19 | $ENV{TEST_NGINX_HTML_DIR} = $HtmlDir; 20 | 21 | no_long_string(); 22 | 23 | log_level('debug'); 24 | 25 | run_tests(); 26 | 27 | __DATA__ 28 | 29 | === TEST 1: small flush_limit, instant flush 30 | --- http_config eval: $::HttpConfig 31 | --- config 32 | location /t { 33 | content_by_lua 'ngx.say("foo")'; 34 | log_by_lua ' 35 | collectgarbage() -- to help leak testing 36 | 37 | local logger = require "resty.logger.socket" 38 | if not logger.initted() then 39 | local ok, err = logger.init{ 40 | host = "127.0.0.1", 41 | port = 29999, 42 | flush_limit = 1, 43 | } 44 | end 45 | 46 | local bytes, err = logger.log(ngx.var.request_uri) 47 | if err then 48 | ngx.log(ngx.ERR, err) 49 | end 50 | '; 51 | } 52 | --- request 53 | GET /t?a=1&b=2 54 | --- wait: 0.1 55 | --- tcp_listen: 29999 56 | --- tcp_reply: 57 | --- no_error_log 58 | [error] 59 | --- tcp_query: /t?a=1&b=2 60 | --- tcp_query_len: 10 61 | --- response_body 62 | foo 63 | 64 | 65 | 66 | === TEST 2: small flush_limit, instant flush, unix domain socket 67 | --- http_config eval: $::HttpConfig 68 | --- config 69 | location /t { 70 | content_by_lua 'ngx.say("foo")'; 71 | log_by_lua ' 72 | collectgarbage() -- to help leak testing 73 | 74 | local logger = require "resty.logger.socket" 75 | if not logger.initted() then 76 | local ok, err = logger.init{ 77 | flush_limit = 1, 78 | path = "$TEST_NGINX_HTML_DIR/logger_test.sock", 79 | } 80 | if not ok then 81 | ngx.log(ngx.ERR, err) 82 | return 83 | end 84 | end 85 | 86 | local bytes, err = logger.log(ngx.var.request_uri) 87 | if err then 88 | ngx.log(ngx.ERR, err) 89 | end 90 | '; 91 | } 92 | --- request 93 | GET /t?a=1&b=2 94 | --- wait: 0.1 95 | --- tcp_listen eval: "$ENV{TEST_NGINX_HTML_DIR}/logger_test.sock" 96 | --- tcp_reply: 97 | --- no_error_log 98 | [error] 99 | --- tcp_query: /t?a=1&b=2 100 | --- tcp_query_len: 10 101 | --- response_body 102 | foo 103 | 104 | 105 | 106 | === TEST 3: small flush_limit, instant flush, write a number to remote 107 | --- http_config eval: $::HttpConfig 108 | --- config 109 | location /t { 110 | content_by_lua 'ngx.say("foo")'; 111 | log_by_lua ' 112 | collectgarbage() -- to help leak testing 113 | 114 | local logger = require "resty.logger.socket" 115 | if not logger.initted() then 116 | local ok, err = logger.init{ 117 | host = "127.0.0.1", 118 | port = 29999, 119 | flush_limit = 1, 120 | } 121 | end 122 | 123 | local bytes, err = logger.log(10) 124 | if err then 125 | ngx.log(ngx.ERR, err) 126 | end 127 | '; 128 | } 129 | --- request 130 | GET /t?a=1&b=2 131 | --- wait: 0.1 132 | --- tcp_listen: 29999 133 | --- tcp_reply: 134 | --- no_error_log 135 | [error] 136 | --- tcp_query: 10 137 | --- tcp_query_len: 2 138 | --- response_body 139 | foo 140 | 141 | 142 | 143 | === TEST 4: buffer log messages, no flush 144 | --- http_config eval: $::HttpConfig 145 | --- config 146 | location /t { 147 | content_by_lua 'ngx.say("foo")'; 148 | log_by_lua ' 149 | collectgarbage() -- to help leak testing 150 | 151 | local logger = require "resty.logger.socket" 152 | if not logger.initted() then 153 | local ok, err = logger.init{ 154 | host = "127.0.0.1", 155 | port = 29999, 156 | flush_limit = 500, 157 | } 158 | end 159 | 160 | local bytes, err = logger.log(ngx.var.request_uri) 161 | if err then 162 | ngx.log(ngx.ERR, err) 163 | end 164 | '; 165 | } 166 | --- request 167 | GET /t?a=1&b=2 168 | --- wait: 0.1 169 | --- tcp_listen: 29999 170 | --- tcp_reply: 171 | --- no_error_log 172 | [error] 173 | lua tcp socket connect 174 | --- response_body 175 | foo 176 | 177 | 178 | 179 | === TEST 5: not initted() 180 | --- http_config eval: $::HttpConfig 181 | --- config 182 | location /t { 183 | content_by_lua 'ngx.say("foo")'; 184 | log_by_lua ' 185 | local logger = require "resty.logger.socket" 186 | 187 | local bytes, err = logger.log(ngx.var.request_uri) 188 | if err then 189 | ngx.log(ngx.ERR, err) 190 | end 191 | '; 192 | } 193 | --- request 194 | GET /t?a=1&b=2 195 | --- error_log 196 | not initialized 197 | --- response_body 198 | foo 199 | 200 | 201 | 202 | === TEST 6: log subrequests 203 | --- http_config eval: $::HttpConfig 204 | --- config 205 | log_subrequest on; 206 | location /t { 207 | content_by_lua ' 208 | collectgarbage() -- to help leak testing 209 | 210 | local res = ngx.location.capture("/main?c=1&d=2") 211 | if res.status ~= 200 then 212 | ngx.log(ngx.ERR, "capture /main failed") 213 | end 214 | ngx.print(res.body) 215 | '; 216 | } 217 | 218 | location /main { 219 | content_by_lua ' 220 | ngx.say("foo") 221 | '; 222 | 223 | log_by_lua ' 224 | local logger = require "resty.logger.socket" 225 | if not logger.initted() then 226 | local ok, err = logger.init{ 227 | host = "127.0.0.1", 228 | port = 29999, 229 | flush_limit = 6, 230 | } 231 | end 232 | 233 | local bytes, err = logger.log("in subrequest") 234 | if err then 235 | ngx.log(ngx.ERR, err) 236 | end 237 | '; 238 | } 239 | 240 | --- request 241 | GET /t?a=1&b=2 242 | --- wait: 0.1 243 | --- tcp_listen: 29999 244 | --- tcp_reply: 245 | --- no_error_log 246 | [error] 247 | --- tcp_query: in subrequest 248 | --- tcp_query_len: 13 249 | --- response_body 250 | foo 251 | 252 | 253 | 254 | === TEST 7: log subrequests, small flush_limit, flush twice 255 | --- http_config eval: $::HttpConfig 256 | --- config 257 | log_subrequest on; 258 | location /t { 259 | content_by_lua ' 260 | collectgarbage() -- to help leak testing 261 | 262 | local res = ngx.location.capture("/main?c=1&d=2") 263 | if res.status ~= 200 then 264 | ngx.log(ngx.ERR, "capture /main failed") 265 | end 266 | ngx.print(res.body) 267 | '; 268 | } 269 | 270 | location /main { 271 | content_by_lua ' 272 | ngx.say("foo")'; 273 | } 274 | 275 | log_by_lua ' 276 | local logger = require "resty.logger.socket" 277 | if not logger.initted() then 278 | local ok, err = logger.init{ 279 | host = "127.0.0.1", 280 | port = 29999, 281 | flush_limit = 1, 282 | } 283 | end 284 | 285 | local bytes, err = logger.log(ngx.var.uri) 286 | if err then 287 | ngx.log(ngx.ERR, err) 288 | end 289 | '; 290 | --- request 291 | GET /t?a=1&b=2 292 | --- wait: 0.1 293 | --- tcp_listen: 29999 294 | --- tcp_reply: 295 | --- no_error_log 296 | [error] 297 | --- tcp_query: /main/t 298 | --- tcp_query_len: 7 299 | --- response_body 300 | foo 301 | 302 | 303 | 304 | === TEST 8: do not log subrequests 305 | --- http_config eval: $::HttpConfig 306 | --- config 307 | location /t { 308 | content_by_lua ' 309 | local res = ngx.location.capture("/main?c=1&d=2") 310 | if res.status ~= 200 then 311 | ngx.log(ngx.ERR, "capture /main failed") 312 | end 313 | ngx.print(res.body) 314 | '; 315 | } 316 | 317 | location /main { 318 | content_by_lua ' 319 | ngx.say("foo") 320 | '; 321 | log_by_lua ' 322 | ngx.log(ngx.NOTICE, "enter log_by_lua") 323 | local logger = require "resty.logger.socket" 324 | if not logger.initted() then 325 | local ok, err = logger.init{ 326 | host = "127.0.0.1", 327 | port = 29999, 328 | flush_limit = 1, 329 | } 330 | end 331 | 332 | local bytes, err = logger.log(ngx.var.request_uri) 333 | if err then 334 | ngx.log(ngx.ERR, err) 335 | end 336 | '; 337 | } 338 | 339 | --- request 340 | GET /t?a=1&b=2 341 | --- wait: 0.1 342 | --- tcp_listen: 29999 343 | --- tcp_reply: 344 | --- no_error_log 345 | [error] 346 | lua tcp socket connect 347 | --- response_body 348 | foo 349 | 350 | 351 | 352 | === TEST 9: bad user config 353 | --- http_config eval: $::HttpConfig 354 | --- config 355 | location /t { 356 | content_by_lua 'ngx.say("foo")'; 357 | log_by_lua ' 358 | local logger = require "resty.logger.socket" 359 | if not logger.initted() then 360 | local ok, err = logger.init("hello") 361 | if not ok then 362 | ngx.log(ngx.ERR, err) 363 | return 364 | end 365 | 366 | end 367 | 368 | local bytes, err = logger.log(ngx.var.request_uri) 369 | if err then 370 | ngx.log(ngx.ERR, err) 371 | end 372 | '; 373 | } 374 | --- request 375 | GET /t?a=1&b=2 376 | --- wait: 0.1 377 | --- error_log 378 | user_config must be a table 379 | --- response_body 380 | foo 381 | 382 | 383 | 384 | === TEST 10: bad user config: no host/port or path 385 | --- http_config eval: $::HttpConfig 386 | --- config 387 | location /t { 388 | content_by_lua 'ngx.say("foo")'; 389 | log_by_lua ' 390 | local logger = require "resty.logger.socket" 391 | if not logger.initted() then 392 | local ok, err = logger.init{ 393 | flush_limit = 1, 394 | drop_limit = 2, 395 | retry_interval = 1, 396 | timeout = 100, 397 | } 398 | if not ok then 399 | ngx.log(ngx.ERR, err) 400 | return 401 | end 402 | end 403 | 404 | local bytes, err = logger.log(ngx.var.request_uri) 405 | if err then 406 | ngx.log(ngx.ERR, err) 407 | end 408 | '; 409 | } 410 | --- request 411 | GET /t?a=1&b=2 412 | --- error_log 413 | no logging server configured. "host"/"port" or "path" is required. 414 | --- response_body 415 | foo 416 | 417 | 418 | 419 | === TEST 11: bad user config: flush_limit > drop_limit 420 | --- http_config eval: $::HttpConfig 421 | --- config 422 | location /t { 423 | content_by_lua 'ngx.say("foo")'; 424 | log_by_lua ' 425 | local logger = require "resty.logger.socket" 426 | if not logger.initted() then 427 | local ok, err = logger.init{ 428 | flush_limit = 2, 429 | drop_limit = 1, 430 | path = "$TEST_NGINX_HTML_DIR/logger_test.sock", 431 | } 432 | if not ok then 433 | ngx.log(ngx.ERR, err) 434 | return 435 | end 436 | end 437 | 438 | local bytes, err = logger.log(ngx.var.request_uri) 439 | if err then 440 | ngx.log(ngx.ERR, err) 441 | end 442 | '; 443 | } 444 | --- request 445 | GET /t?a=1&b=2 446 | --- error_log 447 | "flush_limit" should be < "drop_limit" 448 | --- response_body 449 | foo 450 | 451 | 452 | 453 | === TEST 12: drop log test 454 | --- http_config eval: $::HttpConfig 455 | --- config 456 | location /t { 457 | content_by_lua 'ngx.say("foo")'; 458 | log_by_lua ' 459 | local logger = require "resty.logger.socket" 460 | if not logger.initted() then 461 | local ok, err = logger.init{ 462 | path = "$TEST_NGINX_HTML_DIR/logger_test.sock", 463 | drop_limit = 6, 464 | flush_limit = 4, 465 | } 466 | end 467 | 468 | local bytes, err = logger.log("000") 469 | if err then 470 | ngx.log(ngx.ERR, err) 471 | end 472 | 473 | local bytes, err = logger.log("aaaaa") 474 | if err then 475 | ngx.log(ngx.ERR, err) 476 | end 477 | 478 | local bytes, err = logger.log("bbb") 479 | if err then 480 | ngx.log(ngx.ERR, err) 481 | end 482 | '; 483 | } 484 | --- request 485 | GET /t?a=1&b=2 486 | --- wait: 0.1 487 | --- tcp_listen eval: "$ENV{TEST_NGINX_HTML_DIR}/logger_test.sock" 488 | --- tcp_query: 000bbb 489 | --- tcp_query_len: 6 490 | --- tcp_reply: 491 | --- error_log 492 | logger buffer is full, this log message will be dropped 493 | --- response_body 494 | foo 495 | 496 | 497 | 498 | === TEST 13: logger response 499 | --- http_config eval: $::HttpConfig 500 | --- config 501 | location /t { 502 | content_by_lua ' 503 | collectgarbage() -- to help leak testing 504 | 505 | ngx.say("foo") 506 | local logger = require "resty.logger.socket" 507 | if not logger.initted() then 508 | local ok, err = logger.init{ 509 | host = "127.0.0.1", 510 | port = 29999, 511 | flush_limit = 1, 512 | } 513 | end 514 | 515 | local bytes, err = logger.log(ngx.var.request_uri) 516 | if err then 517 | ngx.log(ngx.ERR, err) 518 | end 519 | ngx.say("wrote bytes: ", bytes) 520 | '; 521 | } 522 | --- request 523 | GET /t?a=1&b=2 524 | --- wait: 0.1 525 | --- tcp_listen: 29999 526 | --- tcp_reply: 527 | --- no_error_log 528 | [error] 529 | --- tcp_query: /t?a=1&b=2 530 | --- tcp_query_len: 10 531 | --- response_body 532 | foo 533 | wrote bytes: 10 534 | 535 | 536 | 537 | === TEST 14: logger response 538 | --- http_config eval: $::HttpConfig 539 | --- config 540 | location /t { 541 | content_by_lua ' 542 | ngx.say("foo") 543 | local logger = require "resty.logger.socket" 544 | if not logger.initted() then 545 | local ok, err = logger.init{ 546 | host = "127.0.0.1", 547 | port = 29999, 548 | flush_limit = 10, 549 | drop_limit = 11, 550 | } 551 | end 552 | 553 | local bytes, err = logger.log(ngx.var.request_uri) 554 | if err then 555 | ngx.log(ngx.ERR, err) 556 | end 557 | -- byte1 should be 0 558 | local bytes1, err1 = logger.log(ngx.var.request_uri) 559 | if err1 then 560 | ngx.log(ngx.ERR, err1) 561 | end 562 | ngx.say("wrote bytes: ", bytes + bytes1) 563 | '; 564 | } 565 | --- request 566 | GET /t?a=1&b=2 567 | --- wait: 0.1 568 | --- tcp_listen: 29999 569 | --- tcp_reply: 570 | --- no_error_log 571 | [error] 572 | --- tcp_query: /t?a=1&b=2 573 | --- tcp_query_len: 10 574 | --- response_body 575 | foo 576 | wrote bytes: 10 577 | 578 | 579 | 580 | === TEST 15: max reuse 581 | --- http_config eval: $::HttpConfig 582 | --- config 583 | location /t { 584 | content_by_lua ' 585 | ngx.say("foo") 586 | local logger = require "resty.logger.socket" 587 | if not logger.initted() then 588 | local ok, err = logger.init{ 589 | host = "127.0.0.1", 590 | port = 29999, 591 | flush_limit = 1, 592 | drop_limit = 10000, 593 | max_buffer_reuse = 1, 594 | } 595 | end 596 | 597 | local bytes, err 598 | local total_bytes = 0 599 | for i = 1, 5 do 600 | bytes, err = logger.log(i .. i .. i) 601 | if err then 602 | ngx.log(ngx.ERR, err) 603 | end 604 | total_bytes = total_bytes + bytes 605 | ngx.sleep(0.05) 606 | end 607 | ngx.say("wrote bytes: ", total_bytes) 608 | '; 609 | } 610 | --- request 611 | GET /t?a=1&b=2 612 | --- wait: 0.1 613 | --- tcp_listen: 29999 614 | --- tcp_reply: 615 | --- error_log 616 | log buffer reuse limit (1) reached, create a new "log_buffer_data" 617 | --- tcp_query: 111222333444555 618 | --- tcp_query_len: 15 619 | --- response_body 620 | foo 621 | wrote bytes: 15 622 | 623 | 624 | 625 | === TEST 16: flush periodically 626 | --- http_config eval: $::HttpConfig 627 | --- config 628 | location /t { 629 | content_by_lua ' 630 | ngx.say("foo") 631 | local logger = require "resty.logger.socket" 632 | if not logger.initted() then 633 | local ok, err = logger.init{ 634 | host = "127.0.0.1", 635 | port = 29999, 636 | flush_limit = 1000, 637 | drop_limit = 10000, 638 | periodic_flush = 0.03, -- 0.03s 639 | } 640 | end 641 | 642 | local bytes, err 643 | bytes, err = logger.log("foo") 644 | if err then 645 | ngx.log(ngx.ERR, err) 646 | end 647 | ngx.say("wrote bytes: ", bytes) 648 | 649 | ngx.sleep(0.05) 650 | 651 | bytes, err = logger.log("bar") 652 | if err then 653 | ngx.log(ngx.ERR, err) 654 | end 655 | ngx.say("wrote bytes: ", bytes) 656 | ngx.sleep(0.05) 657 | '; 658 | } 659 | --- request 660 | GET /t 661 | --- wait: 0.1 662 | --- tcp_listen: 29999 663 | --- tcp_reply: 664 | --- tcp_query: foobar 665 | --- tcp_query_len: 6 666 | --- response_body 667 | foo 668 | wrote bytes: 3 669 | wrote bytes: 3 670 | 671 | 672 | 673 | === TEST 17: SSL logging 674 | --- http_config eval 675 | " 676 | lua_package_path '$::pwd/lib/?.lua;;'; 677 | server { 678 | listen unix:$::HtmlDir/ssl.sock ssl; 679 | server_name test.com; 680 | ssl_certificate $::pwd/t/cert/test.crt; 681 | ssl_certificate_key $::pwd/t/cert/test.key; 682 | 683 | location /test { 684 | lua_need_request_body on; 685 | default_type 'text/plain'; 686 | # 204 No content 687 | content_by_lua ' 688 | ngx.log(ngx.WARN, \"Message received: \", ngx.var.http_message) 689 | ngx.log(ngx.WARN, \"SNI Host: \", ngx.var.ssl_server_name) 690 | ngx.exit(204) 691 | '; 692 | } 693 | } 694 | " 695 | --- config 696 | location /t { 697 | content_by_lua ' 698 | ngx.say("foo") 699 | local logger = require "resty.logger.socket" 700 | if not logger.initted() then 701 | local ok, err = logger.init{ 702 | path = "$TEST_NGINX_HTML_DIR/ssl.sock", 703 | flush_limit = 1, 704 | ssl = true, 705 | ssl_verify = false, 706 | sni_host = "test.com", 707 | } 708 | end 709 | 710 | local bytes, err 711 | bytes, err = logger.log("GET /test HTTP/1.0\\r\\nHost: test.com\\r\\nConnection: close\\r\\nMessage: Hello SSL\\r\\n\\r\\n") 712 | if err then 713 | ngx.log(ngx.ERR, err) 714 | end 715 | ngx.say("wrote bytes: ", bytes) 716 | 717 | ngx.sleep(0.05) 718 | '; 719 | } 720 | --- request 721 | GET /t 722 | --- wait: 0.1 723 | --- response_body 724 | foo 725 | wrote bytes: 77 726 | --- error_log 727 | Message received: Hello SSL 728 | SNI Host: test.com 729 | 730 | 731 | 732 | === TEST 18: SSL logging - Verify 733 | --- http_config eval 734 | " 735 | lua_package_path '$::pwd/lib/?.lua;;'; 736 | server { 737 | listen unix:$::HtmlDir/ssl.sock ssl; 738 | server_name test.com; 739 | ssl_certificate $::pwd/t/cert/test.crt; 740 | ssl_certificate_key $::pwd/t/cert/test.key; 741 | 742 | location /test { 743 | lua_need_request_body on; 744 | default_type 'text/plain'; 745 | # 204 No content 746 | content_by_lua 'ngx.log(ngx.WARN, \"Message received: \", ngx.var.http_message) ngx.exit(204)'; 747 | } 748 | } 749 | " 750 | --- config 751 | location /t { 752 | content_by_lua ' 753 | ngx.say("foo") 754 | local logger = require "resty.logger.socket" 755 | if not logger.initted() then 756 | local ok, err = logger.init{ 757 | path = "$TEST_NGINX_HTML_DIR/ssl.sock", 758 | flush_limit = 1, 759 | ssl = true, 760 | ssl_verify = true, 761 | sni_host = "test.com", 762 | } 763 | end 764 | 765 | local bytes, err 766 | bytes, err = logger.log("GET /test HTTP/1.0\\r\\nHost: test.com\\r\\nConnection: close\\r\\nMessage: Hello SSL\\r\\n\\r\\n") 767 | if err then 768 | ngx.log(ngx.ERR, err) 769 | end 770 | ngx.say("wrote bytes: ", bytes) 771 | 772 | ngx.sleep(0.05) 773 | '; 774 | } 775 | --- request 776 | GET /t 777 | --- wait: 0.1 778 | --- response_body 779 | foo 780 | wrote bytes: 77 781 | --- error_log 782 | lua ssl certificate verify error 783 | 784 | 785 | 786 | === TEST 19: SSL logging - No SNI 787 | --- http_config eval 788 | " 789 | lua_package_path '$::pwd/lib/?.lua;;'; 790 | server { 791 | listen unix:$::HtmlDir/ssl.sock ssl; 792 | server_name test.com; 793 | ssl_certificate $::pwd/t/cert/test.crt; 794 | ssl_certificate_key $::pwd/t/cert/test.key; 795 | 796 | location /test { 797 | lua_need_request_body on; 798 | default_type 'text/plain'; 799 | # 204 No content 800 | content_by_lua ' 801 | ngx.log(ngx.WARN, \"Message received: \", ngx.var.http_message) 802 | ngx.log(ngx.WARN, \"SNI Host: \", ngx.var.ssl_server_name) 803 | ngx.exit(204) 804 | '; 805 | } 806 | } 807 | " 808 | --- config 809 | location /t { 810 | content_by_lua ' 811 | ngx.say("foo") 812 | local logger = require "resty.logger.socket" 813 | if not logger.initted() then 814 | local ok, err = logger.init{ 815 | path = "$TEST_NGINX_HTML_DIR/ssl.sock", 816 | flush_limit = 1, 817 | ssl = true, 818 | ssl_verify = false, 819 | } 820 | end 821 | 822 | local bytes, err 823 | bytes, err = logger.log("GET /test HTTP/1.0\\r\\nHost: test.com\\r\\nConnection: close\\r\\nMessage: Hello SSL\\r\\n\\r\\n") 824 | if err then 825 | ngx.log(ngx.ERR, err) 826 | end 827 | ngx.say("wrote bytes: ", bytes) 828 | 829 | ngx.sleep(0.05) 830 | '; 831 | } 832 | --- request 833 | GET /t 834 | --- wait: 0.1 835 | --- response_body 836 | foo 837 | wrote bytes: 77 838 | --- error_log 839 | Message received: Hello SSL 840 | SNI Host: nil 841 | 842 | 843 | 844 | === TEST 20: Test arguments 845 | --- http_config eval: $::HttpConfig 846 | --- config 847 | location /t { 848 | content_by_lua 'ngx.say("foo")'; 849 | log_by_lua ' 850 | local logger = require "resty.logger.socket" 851 | local ok, err = logger.init{ host = 128, port = 29999 } 852 | ngx.log(ngx.ERR, err) 853 | 854 | local ok, err = logger.init{ host = "google.com", port = "foo" } 855 | ngx.log(ngx.ERR, err) 856 | 857 | local ok, err = logger.init{ host = "google.com", port = 1234567 } 858 | ngx.log(ngx.ERR, err) 859 | 860 | local ok, err = logger.init{ sock_type = true } 861 | ngx.log(ngx.ERR, err) 862 | 863 | local ok, err = logger.init{ sock_type = "upd" } 864 | ngx.log(ngx.ERR, err) 865 | 866 | local ok, err = logger.init{ path = 123 } 867 | ngx.log(ngx.ERR, err) 868 | 869 | local ok, err = logger.init{ path = "/test.sock", flush_limit = "a" } 870 | ngx.log(ngx.ERR, err) 871 | 872 | local ok, err = logger.init{ path = "/test.sock", drop_limit = -2.5 } 873 | ngx.log(ngx.ERR, err) 874 | 875 | local ok, err = logger.init{ path = "/test.sock", timeout = "bar" } 876 | ngx.log(ngx.ERR, err) 877 | 878 | local ok, err = logger.init{ path = "/test.sock", max_retry_times = "bar" } 879 | ngx.log(ngx.ERR, err) 880 | 881 | local ok, err = logger.init{ path = "/test.sock", retry_interval = "bar" } 882 | ngx.log(ngx.ERR, err) 883 | 884 | local ok, err = logger.init{ path = "/test.sock", pool_size = "bar" } 885 | ngx.log(ngx.ERR, err) 886 | 887 | local ok, err = logger.init{ path = "/test.sock", max_buffer_reuse = "bar" } 888 | ngx.log(ngx.ERR, err) 889 | 890 | local ok, err = logger.init{ path = "/test.sock", periodic_flush = "bar" } 891 | ngx.log(ngx.ERR, err) 892 | 893 | local ok, err = logger.init{ path = "/test.sock", ssl = "1" } 894 | ngx.log(ngx.ERR, err) 895 | 896 | local ok, err = logger.init{ path = "/test.sock", ssl_verify = 2 } 897 | ngx.log(ngx.ERR, err) 898 | 899 | local ok, err = logger.init{ path = "/test.sock", sni_host = true } 900 | ngx.log(ngx.ERR, err) 901 | '; 902 | } 903 | --- request 904 | GET /t?a=1&b=2 905 | --- error_log 906 | "host" must be a string 907 | "port" must be a number 908 | "port" out of range 0~65535 909 | "sock_type" must be a string 910 | "sock_type" must be "tcp" or "udp" 911 | "path" must be a string 912 | invalid "flush_limit" 913 | invalid "drop_limit" 914 | invalid "timeout" 915 | invalid "max_retry_times" 916 | invalid "retry_interval" 917 | invalid "pool_size" 918 | invalid "max_buffer_reuse" 919 | invalid "periodic_flush" 920 | "ssl" must be a boolean value 921 | "ssl_verify" must be a boolean value 922 | "sni_host" must be a string 923 | --- response_body 924 | foo 925 | -------------------------------------------------------------------------------- /t/timeout.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et: 2 | 3 | # In this test, we use agentzh.org:12345 to get a connection timeout. Because 4 | # agentzh.org:12345 was configured to drop SYN packet so that connection timeout 5 | # happens 6 | 7 | BEGIN { 8 | if (!defined $ENV{LD_PRELOAD}) { 9 | $ENV{LD_PRELOAD} = ''; 10 | } 11 | 12 | if ($ENV{LD_PRELOAD} !~ /\bmockeagain\.so\b/) { 13 | $ENV{LD_PRELOAD} = "mockeagain.so $ENV{LD_PRELOAD}"; 14 | } 15 | 16 | if ($ENV{MOCKEAGAIN} eq 'r') { 17 | $ENV{MOCKEAGAIN} = 'rw'; 18 | 19 | } else { 20 | $ENV{MOCKEAGAIN} = 'w'; 21 | } 22 | 23 | $ENV{TEST_NGINX_EVENT_TYPE} = 'poll'; 24 | $ENV{MOCKEAGAIN_WRITE_TIMEOUT_PATTERN} = 'hello, world'; 25 | $ENV{TEST_NGINX_POSTPONE_OUTPUT} = 1; 26 | } 27 | 28 | use Test::Nginx::Socket::Lua; 29 | use Cwd qw(cwd); 30 | 31 | repeat_each(2); 32 | no_shuffle(); 33 | 34 | plan tests => repeat_each() * (blocks() * 4 + 3); 35 | our $HtmlDir = html_dir; 36 | 37 | my $pwd = cwd(); 38 | 39 | our $HttpConfig = qq{ 40 | lua_package_path "$pwd/lib/?.lua;;"; 41 | lua_package_cpath "/usr/local/openresty-debug/lualib/?.so;/usr/local/openresty/lualib/?.so;;"; 42 | }; 43 | 44 | $ENV{TEST_NGINX_RESOLVER} = '8.8.8.8'; 45 | $ENV{TEST_NGINX_HTML_DIR} = $HtmlDir; 46 | 47 | no_long_string(); 48 | 49 | log_level('debug'); 50 | 51 | run_tests(); 52 | 53 | __DATA__ 54 | 55 | === TEST 1: connect timeout 56 | --- http_config eval: $::HttpConfig 57 | --- config 58 | resolver 8.8.8.8; 59 | location /t { 60 | content_by_lua ' 61 | -- visit agentzh.org first to get DNS resolve done, then the 62 | -- following tests could use cached resolve result 63 | local sock = ngx.socket.tcp() 64 | sock:settimeout(50) 65 | sock:connect("agentzh.org", 80) 66 | sock:close() 67 | 68 | ngx.say("foo") 69 | '; 70 | log_by_lua ' 71 | local logger = require "resty.logger.socket" 72 | if not logger.initted() then 73 | local ok, err = logger.init{ 74 | -- timeout 1ms 75 | host = "agentzh.org", 76 | port = 12345, 77 | flush_limit = 1, 78 | timeout = 1 } 79 | end 80 | 81 | local bytes, err = logger.log(ngx.var.request_uri) 82 | if err then 83 | ngx.log(ngx.ERR, err) 84 | end 85 | '; 86 | } 87 | --- request 88 | GET /t?a=1&b=2 89 | --- wait: 0.1 90 | --- error_log 91 | tcp socket connect timed out 92 | reconnect to the log server 93 | --- response_body 94 | foo 95 | 96 | 97 | 98 | === TEST 2: send timeout 99 | --- http_config eval: $::HttpConfig 100 | --- config 101 | location /t { 102 | content_by_lua 'ngx.say("foo")'; 103 | log_by_lua ' 104 | local logger = require "resty.logger.socket" 105 | if not logger.initted() then 106 | local ok, err = logger.init{ 107 | host = "127.0.0.1", 108 | port = 29999, 109 | flush_limit = 1, 110 | retry_interval = 1, 111 | timeout = 100, 112 | } 113 | end 114 | 115 | local bytes, err = logger.log("hello, worldaaa") 116 | if err then 117 | ngx.log(ngx.ERR, "log failed") 118 | end 119 | '; 120 | } 121 | --- request 122 | GET /t?a=1&b=2 123 | --- wait: 0.1 124 | --- tcp_listen: 29999 125 | --- tcp_reply: 126 | --- error_log 127 | lua tcp socket write timed out 128 | resend log messages to the log server: timeout 129 | --- response_body 130 | foo 131 | 132 | 133 | 134 | === TEST 3: risk condition 135 | --- http_config eval: $::HttpConfig 136 | --- config 137 | location /t { 138 | content_by_lua 'ngx.say("foo")'; 139 | log_by_lua ' 140 | local logger = require "resty.logger.socket" 141 | if not logger.initted() then 142 | local ok, err = logger.init{ 143 | retry_interval = 1, 144 | host = "127.0.0.1", 145 | port = 29999, 146 | flush_limit = 1 147 | } 148 | end 149 | 150 | local bytes, err = logger.log("1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950") 151 | local bytes, err = logger.log("1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950") 152 | if err then 153 | ngx.log(ngx.ERR, "log failed") 154 | end 155 | '; 156 | } 157 | --- request 158 | GET /t 159 | --- wait: 0.1 160 | --- tcp_listen: 29999 161 | --- tcp_reply: 162 | --- no_error_log 163 | [error] 164 | [warn] 165 | --- tcp_query: 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849501234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950 166 | --- tcp_query_len: 182 167 | --- response_body 168 | foo 169 | 170 | 171 | 172 | === TEST 4: return previous log error 173 | --- http_config eval: $::HttpConfig 174 | --- config 175 | resolver 8.8.8.8; 176 | log_subrequest on; 177 | location /main { 178 | content_by_lua ' 179 | -- visit agentzh.org first to get DNS resolve done, then the 180 | -- following tests could use cached resolve result 181 | local sock = ngx.socket.tcp() 182 | sock:settimeout(500) 183 | sock:connect("agentzh.org", 80) 184 | sock:close() 185 | 186 | local res1 = ngx.location.capture("/t?a=1&b=2") 187 | if res1.status == 200 then 188 | ngx.print(res1.body) 189 | end 190 | 191 | ngx.sleep(0.05) 192 | ngx.say("bar") 193 | 194 | local res3 = ngx.location.capture("/t?a=1&b=2") 195 | if res3.status == 200 then 196 | ngx.print(res3.body) 197 | end 198 | '; 199 | } 200 | location /t { 201 | content_by_lua 'ngx.say("foo")'; 202 | log_by_lua ' 203 | local logger = require "resty.logger.socket" 204 | if not logger.initted() then 205 | local ok, err = logger.init{ 206 | -- timeout 1ms 207 | host = "agentzh.org", 208 | port = 12345, 209 | flush_limit = 1, 210 | timeout = 1, 211 | max_error = 2, 212 | max_retry_times = 1, 213 | retry_interval = 1, 214 | } 215 | end 216 | 217 | local bytes, err = logger.log(ngx.var.request_uri) 218 | if err then 219 | ngx.log(ngx.ERR, "log error:" .. err) 220 | end 221 | '; 222 | } 223 | --- request 224 | GET /main 225 | --- wait: 0.2 226 | --- error_log 227 | lua tcp socket connect timed out 228 | reconnect to the log server: timeout 229 | log error:try to send log messages to the log server failed after 1 retries: try to connect to the log server failed after 1 retries: timeout 230 | --- response_body 231 | foo 232 | bar 233 | foo 234 | 235 | 236 | 237 | === TEST 5: flush race condition 238 | --- http_config eval: $::HttpConfig 239 | --- config 240 | resolver 8.8.8.8; 241 | location /t { 242 | content_by_lua ' 243 | -- visit agentzh.org first to get DNS resolve done, then the 244 | -- following tests could use cached resolve result 245 | local sock = ngx.socket.tcp() 246 | sock:settimeout(500) 247 | sock:connect("agentzh.org", 80) 248 | sock:close() 249 | 250 | ngx.say("foo") 251 | '; 252 | log_by_lua ' 253 | 254 | local logger = require "resty.logger.socket" 255 | if not logger.initted() then 256 | local ok, err = logger.init{ 257 | -- timeout 1ms 258 | host = "127.0.0.1", 259 | port = 29999, 260 | flush_limit = 1, 261 | timeout = 50, 262 | max_retry_times = 2, 263 | retry_interval = 100, 264 | } 265 | end 266 | 267 | local bytes, err = logger.log("hello, worldaaa") 268 | if err then 269 | ngx.log(ngx.ERR, err) 270 | end 271 | 272 | local bytes, err = logger.log("hello, worldbbb") 273 | if err then 274 | ngx.log(ngx.ERR, err) 275 | end 276 | '; 277 | } 278 | --- request 279 | GET /t?a=1&b=2 280 | --- wait: 2 281 | --- tcp_listen: 29999 282 | --- tcp_reply: 283 | --- error_log 284 | previous flush not finished 285 | --- no_error_log 286 | tcp socket connect timed out 287 | --- response_body 288 | foo 289 | --- timeout: 10 290 | -------------------------------------------------------------------------------- /valgrind.suppress: -------------------------------------------------------------------------------- 1 | { 2 | 3 | Memcheck:Addr1 4 | fun:ngx_init_cycle 5 | fun:ngx_master_process_cycle 6 | fun:main 7 | } 8 | { 9 | 10 | Memcheck:Addr4 11 | fun:ngx_init_cycle 12 | fun:ngx_master_process_cycle 13 | fun:main 14 | } 15 | 16 | { 17 | 18 | Memcheck:Cond 19 | fun:lj_str_new 20 | } 21 | { 22 | 23 | Memcheck:Cond 24 | fun:lj_str_new 25 | fun:countint 26 | fun:index2adr 27 | fun:lua_type 28 | fun:ngx_http_lua_ngx_echo 29 | } 30 | { 31 | 32 | Memcheck:Cond 33 | fun:lj_str_new 34 | obj:* 35 | } 36 | { 37 | 38 | Memcheck:Cond 39 | fun:ngx_vslprintf 40 | fun:ngx_snprintf 41 | fun:ngx_sock_ntop 42 | fun:ngx_event_accept 43 | fun:ngx_epoll_process_events 44 | fun:ngx_process_events_and_timers 45 | } 46 | { 47 | 48 | Memcheck:Cond 49 | fun:ngx_vslprintf 50 | fun:ngx_snprintf 51 | fun:ngx_sock_ntop 52 | fun:ngx_event_accept 53 | fun:ngx_epoll_process_events 54 | fun:ngx_process_events_and_timers 55 | } 56 | { 57 | 58 | Memcheck:Addr1 59 | fun:ngx_vslprintf 60 | fun:ngx_snprintf 61 | fun:ngx_sock_ntop 62 | fun:ngx_event_accept 63 | } 64 | { 65 | 66 | exp-sgcheck:SorG 67 | fun:lj_str_new 68 | fun:lua_pushlstring 69 | } 70 | { 71 | 72 | exp-sgcheck:SorG 73 | fun:lj_str_new 74 | fun:lua_pushlstring 75 | } 76 | { 77 | 78 | exp-sgcheck:SorG 79 | fun:ngx_http_lua_ndk_set_var_get 80 | } 81 | { 82 | 83 | exp-sgcheck:SorG 84 | fun:lj_str_new 85 | fun:lua_getfield 86 | } 87 | { 88 | 89 | exp-sgcheck:SorG 90 | fun:lj_str_new 91 | fun:lua_setfield 92 | } 93 | { 94 | 95 | exp-sgcheck:SorG 96 | fun:ngx_http_variables_init_vars 97 | fun:ngx_http_block 98 | } 99 | { 100 | 101 | exp-sgcheck:SorG 102 | fun:ngx_conf_parse 103 | } 104 | { 105 | 106 | exp-sgcheck:SorG 107 | fun:ngx_vslprintf 108 | fun:ngx_log_error_core 109 | } 110 | { 111 | 112 | Memcheck:Addr4 113 | fun:lj_str_new 114 | fun:lua_setfield 115 | } 116 | { 117 | 118 | Memcheck:Addr4 119 | fun:lj_str_new 120 | fun:lua_getfield 121 | } 122 | { 123 | 124 | Memcheck:Param 125 | epoll_ctl(event) 126 | fun:epoll_ctl 127 | } 128 | { 129 | 130 | Memcheck:Cond 131 | fun:ngx_conf_flush_files 132 | fun:ngx_single_process_cycle 133 | } 134 | { 135 | 136 | Memcheck:Cond 137 | fun:memcpy 138 | fun:ngx_vslprintf 139 | fun:ngx_log_error_core 140 | fun:ngx_http_charset_header_filter 141 | } 142 | { 143 | 144 | Memcheck:Addr4 145 | fun:lj_str_new 146 | fun:lua_pushlstring 147 | } 148 | { 149 | 150 | Memcheck:Cond 151 | fun:lj_str_new 152 | fun:lj_str_fromnum 153 | } 154 | { 155 | 156 | Memcheck:Cond 157 | fun:lj_str_new 158 | fun:lua_pushlstring 159 | } 160 | { 161 | 162 | Memcheck:Addr4 163 | fun:lj_str_new 164 | fun:lua_setfield 165 | fun:ngx_http_lua_cache_store_code 166 | } 167 | { 168 | 169 | Memcheck:Cond 170 | fun:lj_str_new 171 | fun:lua_getfield 172 | fun:ngx_http_lua_cache_load_code 173 | } 174 | { 175 | 176 | Memcheck:Cond 177 | fun:lj_str_new 178 | fun:lua_setfield 179 | fun:ngx_http_lua_cache_store_code 180 | } 181 | { 182 | 183 | Memcheck:Addr4 184 | fun:lj_str_new 185 | fun:lua_getfield 186 | fun:ngx_http_lua_cache_load_code 187 | } 188 | { 189 | 190 | Memcheck:Param 191 | socketcall.setsockopt(optval) 192 | fun:setsockopt 193 | fun:drizzle_state_connect 194 | } 195 | { 196 | 197 | Memcheck:Cond 198 | fun:ngx_conf_flush_files 199 | fun:ngx_single_process_cycle 200 | fun:main 201 | } 202 | { 203 | 204 | Memcheck:Leak 205 | fun:malloc 206 | fun:ngx_alloc 207 | fun:ngx_event_process_init 208 | } 209 | { 210 | 211 | Memcheck:Cond 212 | fun:index 213 | fun:expand_dynamic_string_token 214 | fun:_dl_map_object 215 | fun:map_doit 216 | fun:_dl_catch_error 217 | fun:do_preload 218 | fun:dl_main 219 | fun:_dl_sysdep_start 220 | fun:_dl_start 221 | } 222 | --------------------------------------------------------------------------------