5.Skynet/examples程序分析之globallog與userlog

一、概述

Skynet 提供了靈活的日志系統(tǒng),主要通過兩種方式實(shí)現(xiàn):

方式 示例 特點(diǎn)
服務(wù)型日志 globallog.lua 作為獨(dú)立服務(wù),通過消息調(diào)用記錄日志
協(xié)議型日志 userlog.lua 通過注冊協(xié)議,攔截特定類型消息

核心概念

  • 服務(wù)注冊skynet.register() 將服務(wù)注冊為可調(diào)用的實(shí)體
  • 協(xié)議注冊skynet.register_protocol() 擴(kuò)展消息處理能力
  • 本地 vs 全局:服務(wù)名以 . 開頭為本地注冊,否則為全局注冊

二、運(yùn)行說明

2.1 globallog 使用方式

配置方式:無需特殊配置,直接啟動(dòng)服務(wù)即可

-- 在 main.lua 中啟動(dòng)
skynet.newservice("globallog")

使用方式

-- 方式1:使用本地注冊名
skynet.send(".log", "lua", "Hello World")

-- 方式2:使用全局注冊名
skynet.send("LOG", "lua", "Hello World")

2.2 userlog 使用方式

配置方式:在 config 文件中配置 logger

logger = "userlog"
logservice = "snlua"
logpath = "."

使用方式

-- 通過 skynet.error 輸出日志
skynet.error("This is an error message")

-- 通過 TEXT 協(xié)議發(fā)送消息
skynet.send(service_addr, "text", "This is a text message")

三、架構(gòu)結(jié)構(gòu)圖

3.1 globallog 架構(gòu)

┌─────────────────────────────────────────────────────────────────┐
│                      服務(wù)型日志架構(gòu)                              │
└─────────────────────────────────────────────────────────────────┘

   ┌──────────────┐      ┌──────────────┐      ┌──────────────┐
   │  Service A   │      │  Service B   │      │  Service C   │
   └──────┬───────┘      └──────┬───────┘      └──────┬───────┘
          │                     │                     │
          │ skynet.send(".log") │                     │
          │                     │ skynet.send("LOG")  │
          │                     │                     │ skynet.send(".log")
          ▼                     ▼                     ▼
   ┌───────────────────────────────────────────────────────────┐
   │                   GlobalLog Service                      │
   │  ┌───────────────────────────────────────────────────┐   │
   │  │ skynet.dispatch("lua", handler)                   │   │
   │  │   └─ skynet.register(".log")                      │   │
   │  │   └─ skynet.register("LOG")                       │   │
   │  │   └─ print("[GLOBALLOG]", address, ...)           │   │
   │  └───────────────────────────────────────────────────┘   │
   └───────────────────────────────────────────────────────────┘
                            │
                            ▼
                   ┌──────────────┐
                   │   Console    │
                   └──────────────┘

3.2 userlog 架構(gòu)

┌─────────────────────────────────────────────────────────────────┐
│                      協(xié)議型日志架構(gòu)                              │
└─────────────────────────────────────────────────────────────────┘

   ┌─────────────────────────────────────────────────────────────┐
   │                   消息分發(fā)層                                │
   │  ┌─────────────────────────────────────────────────────┐   │
   │  │ PTYPE_TEXT → userlog.dispatch()                     │   │
   │  │ PTYPE_SYSTEM → userlog.dispatch()                   │   │
   │  └─────────────────────────────────────────────────────┘   │
   └─────────────────────────────────────────────────────────────┘
                              │
                              ▼
                   ┌──────────────────┐
                   │   userlog.lua    │
                   │  - TEXT 協(xié)議處理  │
                   │  - SYSTEM 協(xié)議處理│
                   └──────────────────┘
                              │
                              ▼
                   ┌──────────────────┐
                   │     Console      │
                   └──────────────────┘

   ┌──────────────┐      ┌──────────────┐      ┌──────────────┐
   │  Service A   │      │  Service B   │      │  Service C   │
   └──────┬───────┘      └──────┬───────┘      └──────┬───────┘
          │                     │                     │
          │ skynet.error(msg)   │ skynet.send(addr,  │
          │                     │ "text", msg)        │
          └─────────────────────┴─────────────────────┘

四、源碼解析

4.1 globallog.lua 解析

local skynet = require "skynet"
require "skynet.manager"    -- import skynet.register

skynet.start(function()
    -- 注冊消息分發(fā)函數(shù)
    skynet.dispatch("lua", function(session, address, ...)
        -- 打印日志:包含發(fā)送者地址和消息內(nèi)容
        print("[GLOBALLOG]", skynet.address(address), ...)
    end)
    
    -- 注冊本地服務(wù)名(僅當(dāng)前節(jié)點(diǎn)可見)
    skynet.register ".log"
    
    -- 注冊全局服務(wù)名(整個(gè)集群可見)
    skynet.register "LOG"
end)

關(guān)鍵技術(shù)點(diǎn)

代碼行 說明
require "skynet.manager" 引入 skynet.register 函數(shù)
skynet.dispatch("lua", ...) 注冊 LUA 協(xié)議消息處理器
skynet.address(address) 將服務(wù)地址轉(zhuǎn)換為可讀格式
skynet.register(".log") 本地注冊,僅當(dāng)前節(jié)點(diǎn)可用
skynet.register("LOG") 全局注冊,整個(gè)集群可用

4.2 userlog.lua 解析

local skynet = require "skynet"
require "skynet.manager"

-- 注冊 TEXT 協(xié)議(必須在 skynet.start 之前注冊)
skynet.register_protocol {
    name = "text",              -- 協(xié)議名稱
    id = skynet.PTYPE_TEXT,     -- 協(xié)議類型 ID(預(yù)定義常量)
    unpack = skynet.tostring,   -- 解包函數(shù):將二進(jìn)制轉(zhuǎn)為字符串
    dispatch = function(_, address, msg)
        -- 格式化輸出:地址 + 時(shí)間 + 消息
        print(string.format(":%08x(%.2f): %s", address, skynet.time(), msg))
    end
}

-- 注冊 SYSTEM 協(xié)議
skynet.register_protocol {
    name = "SYSTEM",
    id = skynet.PTYPE_SYSTEM,
    unpack = function(...) return ... end,  -- 不解包,原樣傳遞
    dispatch = function()
        -- 處理系統(tǒng)信號(hào)(如 SIGHUP)
        print("SIGHUP")
    end
}

skynet.start(function()
    -- 用戶日志服務(wù)不需要做任何初始化
    -- 協(xié)議已在啟動(dòng)前注冊完成
end)

關(guān)鍵技術(shù)點(diǎn)

代碼行 說明
skynet.register_protocol {...} 注冊自定義協(xié)議
name = "text" 協(xié)議名稱,用于調(diào)試和標(biāo)識(shí)
id = skynet.PTYPE_TEXT 協(xié)議 ID,必須使用預(yù)定義值
unpack = skynet.tostring 將二進(jìn)制消息轉(zhuǎn)為字符串
skynet.time() 獲取當(dāng)前時(shí)間(單位:秒)
PTYPE_SYSTEM 系統(tǒng)信號(hào)協(xié)議,處理進(jìn)程信號(hào)

4.3 協(xié)議注冊時(shí)機(jī)

重要注意:協(xié)議注冊必須在 skynet.start() 之前完成:

-- ? 正確:在 start 之前注冊協(xié)議
skynet.register_protocol {
    name = "text",
    id = skynet.PTYPE_TEXT,
    ...
}

skynet.start(function()
    -- 服務(wù)啟動(dòng)邏輯
end)

-- ? 錯(cuò)誤:在 start 之后注冊無效
skynet.start(function()
    skynet.register_protocol { ... }  -- 不會(huì)生效
end)

五、實(shí)用舉例

5.1 基礎(chǔ)日志使用

示例1:使用 globallog

-- main.lua
local skynet = require "skynet"

skynet.start(function()
    -- 啟動(dòng)全局日志服務(wù)
    local log = skynet.newservice("globallog")
    
    -- 使用本地名稱發(fā)送日志
    skynet.send(".log", "lua", "Server started")
    
    -- 使用全局名稱發(fā)送日志
    skynet.send("LOG", "lua", "User login", "user123")
    
    skynet.exit()
end)

示例2:使用 userlog

-- main.lua
local skynet = require "skynet"

skynet.start(function()
    -- userlog 會(huì)自動(dòng)處理 TEXT 協(xié)議消息
    skynet.error("This is a standard error message")
    
    -- 直接發(fā)送 TEXT 協(xié)議消息
    local addr = skynet.self()
    skynet.send(addr, "text", "This is a text protocol message")
    
    skynet.exit()
end)

5.2 自定義日志服務(wù)

場景:創(chuàng)建一個(gè)帶日志級(jí)別和時(shí)間戳的日志服務(wù)

-- custom_logger.lua
local skynet = require "skynet"
require "skynet.manager"

local LOG_LEVEL = {
    DEBUG = 1,
    INFO = 2,
    WARN = 3,
    ERROR = 4
}

local current_level = LOG_LEVEL.DEBUG

local function get_time()
    return os.date("%Y-%m-%d %H:%M:%S")
end

local function log(level, level_name, ...)
    if level >= current_level then
        print(string.format("[%s][%s] %s", get_time(), level_name, table.concat({...}, " ")))
    end
end

skynet.start(function()
    skynet.dispatch("lua", function(session, address, cmd, ...)
        cmd = cmd:upper()
        if cmd == "DEBUG" then
            log(LOG_LEVEL.DEBUG, "DEBUG", ...)
        elseif cmd == "INFO" then
            log(LOG_LEVEL.INFO, "INFO", ...)
        elseif cmd == "WARN" then
            log(LOG_LEVEL.WARN, "WARN", ...)
        elseif cmd == "ERROR" then
            log(LOG_LEVEL.ERROR, "ERROR", ...)
        elseif cmd == "SETLEVEL" then
            local level = ...
            current_level = LOG_LEVEL[level:upper()] or LOG_LEVEL.DEBUG
        end
    end)
    skynet.register ".logger"
end)

使用方式

local logger = skynet.newservice("custom_logger")
skynet.send(".logger", "lua", "INFO", "Server started")
skynet.send(".logger", "lua", "DEBUG", "Debug info")
skynet.send(".logger", "lua", "SETLEVEL", "WARN")  -- 設(shè)置日志級(jí)別

5.3 文件日志輸出

場景:將日志寫入文件

-- file_logger.lua
local skynet = require "skynet"
require "skynet.manager"

local log_file

skynet.start(function()
    local logpath = skynet.getenv("logpath") or "."
    local filename = logpath .. "/server.log"
    log_file = io.open(filename, "a")
    
    skynet.dispatch("lua", function(session, address, ...)
        local msg = table.concat({...}, " ")
        local time = os.date("%Y-%m-%d %H:%M:%S")
        local line = string.format("[%s][%s] %s\n", time, skynet.address(address), msg)
        
        if log_file then
            log_file:write(line)
            log_file:flush()  -- 立即寫入
        end
        
        -- 同時(shí)輸出到控制臺(tái)
        print(line:sub(1, -2))  -- 去掉換行符
    end)
    skynet.register ".filelogger"
end)

function exit()
    if log_file then
        log_file:close()
    end
end

六、關(guān)鍵技術(shù)對比

6.1 globallog vs userlog

特性 globallog userlog
類型 服務(wù)(Service) 協(xié)議處理器(Protocol Handler)
注冊方式 skynet.register() skynet.register_protocol()
觸發(fā)方式 主動(dòng)調(diào)用 skynet.send() 發(fā)送特定協(xié)議消息自動(dòng)觸發(fā)
適用場景 應(yīng)用層日志記錄 系統(tǒng)級(jí)消息處理
靈活性 高(可自定義命令) 中(協(xié)議固定)
性能 需消息傳遞 直接處理,更快

6.2 本地注冊 vs 全局注冊

特性 本地注冊(.name) 全局注冊(NAME)
作用域 當(dāng)前節(jié)點(diǎn) 整個(gè)集群
注冊函數(shù) skynet.register(".name") skynet.register("NAME")
查找方式 skynet.localname() skynet.queryservice()
名稱限制 無特殊限制 長度 < 16,不能是數(shù)字
性能 本地查找,更快 需經(jīng)過集群路由

6.3 預(yù)定義協(xié)議類型

協(xié)議 ID 名稱 用途
PTYPE_TEXT text 文本消息
PTYPE_SYSTEM SYSTEM 系統(tǒng)信號(hào)
PTYPE_LUA lua Lua 消息(默認(rèn))
PTYPE_RESPONSE response 響應(yīng)消息
PTYPE_ERROR error 錯(cuò)誤消息
PTYPE_CLIENT client 客戶端協(xié)議

七、日志系統(tǒng)工作原理

7.1 消息分發(fā)流程

消息發(fā)送                    消息路由                    消息處理
     │                          │                          │
     │ skynet.send(addr, type)  │                          │
     ▼                          ▼                          ▼
┌──────────────┐          ┌──────────────┐          ┌──────────────┐
│ 消息封裝     │          │ 協(xié)議路由     │          │ 協(xié)議處理     │
│ pack(msg)    │ ───────→ │ 查找協(xié)議ID   │ ───────→ │ unpack(msg)  │
└──────────────┘          │ 定位目標(biāo)服務(wù)  │          │ dispatch()   │
                          └──────────────┘          └──────────────┘

7.2 logger 配置機(jī)制

當(dāng)配置 logger = "userlog" 時(shí):

-- config 文件
logger = "userlog"      -- 指定日志服務(wù)名
logservice = "snlua"    -- 指定服務(wù)類型
logpath = "."           -- 日志路徑

-- 底層處理流程
skynet.error(msg)
    │
    ├─→ 如果配置了 logger
    │       └─→ 發(fā)送到 logger 服務(wù)
    │               └─→ logger 服務(wù)處理并輸出
    │
    └─→ 否則直接輸出到控制臺(tái)

八、最佳實(shí)踐建議

8.1 選擇合適的日志方式

-- 場景1:簡單日志記錄 → 使用 globallog
skynet.send(".log", "lua", "Message")

-- 場景2:系統(tǒng)級(jí)消息 → 使用 userlog(TEXT 協(xié)議)
skynet.send(addr, "text", "Message")

-- 場景3:結(jié)構(gòu)化日志 → 自定義日志服務(wù)
skynet.send(".logger", "lua", "INFO", "Message")

8.2 日志服務(wù)部署策略

生產(chǎn)環(huán)境建議:
┌──────────────────────────────────────────────────────────┐
│                    日志服務(wù)集群                            │
├──────────────────────────────────────────────────────────┤
│  ┌──────────┐  ┌──────────┐  ┌──────────┐                │
│  │ logger1  │  │ logger2  │  │ logger3  │                │
│  │ (文件)    │  │ (文件)    │  │ (控制臺(tái)) │                │
│  └──────────┘  └──────────┘  └──────────┘                │    
│          │          │          │                         │
│          └──────────┼──────────┘                         │
│                     ▼                                    │
│              ┌──────────────┐                            │
│              │ 日志聚合服務(wù)  │                             │
│              │ (定時(shí)輪轉(zhuǎn))    │                            │
│              └──────────────┘                            │
└──────────────────────────────────────────────────────────┘

8.3 性能優(yōu)化建議

  1. 異步日志:使用 skynet.send() 而非 skynet.call()
  2. 批量寫入:累積多條日志后一次性寫入文件
  3. 日志級(jí)別控制:生產(chǎn)環(huán)境關(guān)閉 DEBUG 級(jí)別
  4. 避免阻塞:日志處理不要做耗時(shí)操作

九、常見問題與解決方案

9.1 日志服務(wù)找不到

問題現(xiàn)象skynet.send(".log", ...) 報(bào)錯(cuò) "service not found"

解決方案

-- 確保先啟動(dòng)日志服務(wù)
local log = skynet.newservice("globallog")

-- 或者使用 pcall 保護(hù)
local ok = pcall(skynet.send, ".log", "lua", "msg")
if not ok then
    print("Log service not available")
end

9.2 協(xié)議注冊不生效

問題現(xiàn)象:注冊的協(xié)議沒有處理消息

解決方案

-- 確保在 skynet.start 之前注冊協(xié)議
skynet.register_protocol {
    name = "text",
    id = skynet.PTYPE_TEXT,
    ...
}

skynet.start(function()
    -- 協(xié)議已生效
end)

9.3 日志文件過大

解決方案

-- 實(shí)現(xiàn)日志輪轉(zhuǎn)
local function rotate_log()
    local current = os.date("%Y%m%d")
    if current ~= last_date then
        -- 關(guān)閉舊文件,打開新文件
        log_file:close()
        log_file = io.open("log_" .. current .. ".txt", "a")
        last_date = current
    end
end

十、總結(jié)

10.1 核心要點(diǎn)

  1. globallog:服務(wù)型日志,通過消息調(diào)用記錄,靈活可控
  2. userlog:協(xié)議型日志,自動(dòng)攔截 TEXT 協(xié)議消息
  3. 協(xié)議注冊:必須在 skynet.start() 之前完成
  4. 服務(wù)注冊:本地注冊(. 前綴)vs 全局注冊

10.2 選擇建議

場景 推薦方案
應(yīng)用層業(yè)務(wù)日志 globallog 或自定義日志服務(wù)
系統(tǒng)級(jí)消息處理 userlog(TEXT/SYSTEM 協(xié)議)
集群環(huán)境 全局注冊的日志服務(wù)
高性能要求 自定義異步日志服務(wù)
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容