
Lua 常用於嵌入式、遊戲、Nginx/OpenResty 等高效能場景。寫 Lua 雖然輕巧,但遇到 bug 沒有好工具很痛苦。這篇教你用 VSCode 打造現代化 Lua 除錯環境,讓你能設斷點、單步執行、即時觀察變數,效率大提升!
本專案實際採用 VSCode + Docker + EmmyLua 進行 Lua 斷點除錯,以下是完整實戰流程與設定:
docker-compose.yml
:定義 openresty 服務、埠號對應、volume 掛載conf/nginx.conf
:Nginx 與 OpenResty 設定lua/myapp.lua
:主要 Lua 業務邏輯與偵錯入口lua/.vscode/launch.json
:VSCode 偵錯設定docker-compose.yml
需包含:
version: '3.8' services: openresty: build: . container_name: openresty-dev ports: - "8080:80" # HTTP - "8081:443" # HTTPS - "9966:9966" # EmmyLua Debugger (Lua 除錯) volumes: - ./conf/nginx.conf:/usr/local/openresty/nginx/conf/nginx.conf:ro - ./lua:/usr/local/openresty/nginx/lua - ./logs:/usr/local/openresty/nginx/logs - ./db/GeoLite2-City.mmdb:/usr/local/openresty/nginx/lua/GeoLite2-City.mmdb:ro environment: TZ: Asia/Taipei restart: unless-stopped
8080:80
:對外 HTTP 服務8081:443
:對外 HTTPS 服務(如有設定 SSL)9966:9966
:Lua 除錯(EmmyLua Debugger 連接埠)TZ: Asia/Taipei
:設定容器時區,方便日誌對時restart: unless-stopped
:自動重啟容器,除非手動停止以下為本專案用於 OpenResty + Lua 除錯的 Dockerfile 範例,已內建 lua-resty-maxminddb、EmmyLua Debugger、GeoLite2-City 資料庫等:
FROM openresty/openresty:alpine-fat # 安裝必要套件與工具 RUN apk add --no-cache \ git \ build-base \ cmake \ libmaxminddb-dev \ perl \ libmaxminddb \ wget \ tar \ unzip \ luarocks # 安裝 lua-resty-maxminddb RUN luarocks install lua-resty-maxminddb # 設定時區 ENV TZ=Asia/Taipei # 建立資料夾結構 RUN mkdir -p /usr/local/openresty/nginx/lua \ && mkdir -p /usr/local/openresty/nginx/logs \ && mkdir -p /usr/local/openresty/nginx/db # 下載最新 MaxMind GeoLite2-City 資料庫 RUN wget -O /tmp/GeoLite2-City.tar.gz "https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-City&license_key=<YOUR_LICENSE_KEY>&suffix=tar.gz" \ && tar -xzf /tmp/GeoLite2-City.tar.gz -C /tmp \ && find /tmp -name "GeoLite2-City.mmdb" -exec cp {} /usr/local/openresty/nginx/db/ \; \ && rm -rf /tmp/GeoLite2-City.tar.gz /tmp/GeoLite2-City_* # === 下載並編譯 emmy_core.so === WORKDIR /tmp RUN git clone https://github.com/EmmyLua/EmmyLuaDebugger.git \ && cd EmmyLuaDebugger \ && mkdir build && cd build \ && cmake .. -DCMAKE_BUILD_TYPE=Release -DLUA_INCLUDE_DIR=/usr/local/openresty/luajit/include/luajit-2.1 \ && make \ && find . -name emmy_core.so -exec cp {} /usr/local/openresty/lualib/emmy_core.so \; \ && cd / && rm -rf /tmp/EmmyLuaDebugger # 設定 Lua 模組路徑 ENV LUA_PATH="/usr/local/openresty/lualib/?.lua;;" ENV LUA_CPATH="/usr/local/openresty/lualib/?.so;;" # 預設啟動 CMD ["/usr/local/openresty/bin/openresty", "-g", "daemon off;"]
注意:
license_key=<YOUR_LICENSE_KEY>
請至 MaxMind 官網 註冊帳號並取得專屬金鑰。- 此金鑰屬於個人授權,請勿公開於網路或版本控制系統。
- 若未填寫正確金鑰,GeoLite2-City 資料庫將無法下載。
在 lua/myapp.lua
最前面加上:
local dbg = require("emmy_core") dbg.tcpListen("0.0.0.0", 9966) -- 讓容器內偵錯器監聽所有網卡 dbg.waitIDE() -- 等待 IDE 連線才繼續執行 dbg.breakHere() -- 進入斷點
在 lua/.vscode/launch.json
中加入:
{ "version": "0.2.0", "configurations": [ { "type": "emmylua_new", "request": "attach", "name": "Attach by process id", "pid": 0, "processName": "", "captureLog": false, "host": "localhost", "port": 9966, "cwd": "${workspaceFolder}/lua", "ext": [".lua", "lua.txt", ".lua.bytes"] } ] }
host
請設為localhost
,因為我們已將 9966 埠號從容器映射到主機。
docker-compose restart openresty
)。http://localhost:8080/
),程式會自動停在斷點。以 lua/myapp.lua
為例,示範如何設置斷點與觀察變數:
local dbg = require("emmy_core") dbg.tcpListen("0.0.0.0", 9966) dbg.waitIDE() dbg.breakHere() local cjson = require 'cjson' local geo = require 'resty.maxminddb' geo.init("/usr/local/openresty/nginx/lua/GeoLite2-City.mmdb") -- 假設這裡有一個函式要查詢 IP 位置 local function get_country(ip) local res, err = geo.lookup(ip) if not res then ngx.log(ngx.ERR, "Geo lookup error: ", err) return nil end return res end local ip = ngx.var.arg_ip or ngx.var.remote_addr local country_info = get_country(ip) ngx.say(cjson.encode(country_info))
只要在 myapp.lua 開頭插入偵錯程式碼,就能用 VSCode 斷點、單步追蹤每個變數!
接下來,需在 nginx.conf 中設定路由,讓 OpenResty 能將請求導向剛剛的 myapp.lua:
location /lua { default_type 'text/plain'; content_by_lua_file /usr/local/openresty/nginx/lua/myapp.lua; }
這樣設定後,當你訪問 http://localhost:8080/lua 時,Nginx 會將請求導向 myapp.lua 處理,並回應結果。
以下為本專案的 nginx.conf 範例,已包含 myapp.lua 路由與 /get-country 範例,適合直接用於 OpenResty + Lua 除錯環境:
worker_processes 1; events { worker_connections 1024; } http { lua_package_path "/usr/local/openresty/lualib/?.lua;/usr/local/openresty/nginx/lua/?.lua;;"; server { listen 80; server_name localhost; location /lua { default_type 'text/plain'; content_by_lua_file /usr/local/openresty/nginx/lua/myapp.lua; } location /get-country { default_type 'text/plain'; content_by_lua_block { local cjson = require 'cjson' local geo = require 'resty.maxminddb' if not geo.initted() then geo.init("/usr/local/openresty/nginx/lua/GeoLite2-City.mmdb") end local res,err = geo.lookup(ngx.var.arg_ip or ngx.var.remote_addr) --support ipv6 e.g. 2001:4860:0:1001::3004:ef68 if not res then ngx.log(ngx.ERR,'failed to lookup by ip ,reason:',err) end ngx.say("full :",cjson.encode(res)) if ngx.var.arg_node then ngx.say("node name:",ngx.var.arg_node," ,value:", cjson.encode(res[ngx.var.arg_node] or {})) end } } access_log /usr/local/openresty/nginx/logs/access.log; error_log /usr/local/openresty/nginx/logs/error.log; } }
當用戶端(如瀏覽器或 curl)請求 http://localhost:8080/lua 時,Nginx 會將該請求交給 myapp.lua 處理,並將結果回傳給用戶端。你也可以依需求調整路由。
/usr/local/openresty/nginx/logs/error.log
nginx.conf
設 error_log ... debug;
ngx.log(ngx.ERR, "Debug info: ", cjson.encode(var))
dbg.tcpListen("0.0.0.0", 9966)
,不是 localhost
。docker-compose.yml
有沒有 9966:9966
。host
設定為 localhost
。/usr/local/openresty/nginx/logs/error.log
。ngx.log(ngx.ERR, "debug info")
輔助。方法 | 指令/說明 |
---|---|
進容器看日誌 | docker exec -it openresty-dev /bin/sh tail -f /usr/local/openresty/nginx/logs/error.log |
直接看容器 log | docker logs -f openresty-dev |
掛本地目錄 | docker run -v /your/local/logs:/usr/local/openresty/nginx/logs ... openresty |
抓封包看流量 | sudo tcpdump -i docker0 port 8080 |
VSCode Remote | 直接用 VSCode 編輯、debug 容器裡的檔案 |