欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

golang整合日志zap的實(shí)現(xiàn)示例

 更新時(shí)間:2024年10月22日 10:17:41   作者:code:404-not-found  
Go語(yǔ)言中的zap庫(kù)提供了強(qiáng)大的日志管理功能,支持日志記錄到文件、日志切割、多日志級(jí)別、結(jié)構(gòu)化日志輸出等,它通過(guò)三種方法zap.NewProduction()、zap.NewDevelopment()和zap.NewExample(),快速構(gòu)建適用于不同環(huán)境的logger,感興趣的可以了解一下

在許多Go語(yǔ)言項(xiàng)目中,我們需要一個(gè)好的日志記錄器能夠提供下面這些功能:

  • 能夠?qū)⑹录涗浀轿募?,而不是?yīng)用程序控制臺(tái);
  • 日志切割-能夠根據(jù)文件大小、時(shí)間或間隔等來(lái)切割日志文件;
  • 支持不同的日志級(jí)別。例如INFO,DEBUG,ERROR等;
  • 能夠打印基本信息,如調(diào)用文件/函數(shù)名和行號(hào),日志時(shí)間等;
  • 日志分級(jí):有 Debug,Info,Warn,Error,DPanic,Panic,F(xiàn)atal 等
  • 日志記錄結(jié)構(gòu)化:日志內(nèi)容記錄是結(jié)構(gòu)化的,比如 json 格式輸出
  • 自定義格式:用戶可以自定義輸出的日志格式
  • 自定義公共字段:用戶可以自定義公共字段,大家輸出的日志內(nèi)容就共同擁有了這些字段
  • 調(diào)試:可以打印文件名、函數(shù)名、行號(hào)、日志時(shí)間等,便于調(diào)試程序
  • 自定義調(diào)用棧級(jí)別:可以根據(jù)日志級(jí)別輸出它的調(diào)用棧信息
  • Namespace:日志命名空間。定義命名空間后,所有日志內(nèi)容就在這個(gè)命名空間下。命名空間相當(dāng)于一個(gè)文件夾
  • 支持 hook 操作

安裝

go get -u go.uber.org/zap

基本使用

package main

import "go.uber.org/zap"

var logger *zap.Logger

func main() {
	logger, _ = zap.NewProduction() // 使用生產(chǎn)環(huán)境
    // logger, _ := zap.NewDevelopment() // 使用開(kāi)發(fā)環(huán)境
	defer logger.Sync() // 刷新緩存
    
    
    suger := logger.Sugar() // 開(kāi)啟日志語(yǔ)法糖
    
    suger.Debug("我是Debug級(jí)別的日志")
	suger.Debugw("我是Debug級(jí)別的日志", "key1", "value1", "key2", "value2")
	suger.Debugf("我是Debug級(jí)別的日志%s", "value")

	suger.Warn("我是Warn級(jí)別的日志")
	suger.Warnw("我是Warn級(jí)別的日志", "key1", "value1", "key2", "value2")
	suger.Warnf("我是Warn級(jí)別的日志%s", "value")

	suger.Info("我是Info級(jí)別的日志")
	suger.Infow("我是Info級(jí)別的日志", "key1", "value1", "key2", "value2")
	suger.Infof("我是Info級(jí)別的日志 %s", "info")

	suger.Error("我是Error級(jí)別的日志")
	suger.Errorw("我是Error級(jí)別的日志", "key1", "value1", "key2", "value2")
	suger.Errorf("我是Error級(jí)別的日志 %s", "value")

	suger.Panic("我是Panic級(jí)別的日志")
	suger.Panicw("我是Panic級(jí)別的日志", "key1", "value1", "key2", "value2")
	suger.Panicf("我是Panic級(jí)別的日志 %s", "value")
    
    // 使用不帶語(yǔ)法糖的日志
    logger.Debug("我是Debug級(jí)別的日志", zap.String("key", "value"), zap.Int("num", 10))
	logger.Warn("我是Warn級(jí)別的日志", zap.String("key", "value"), zap.Int("num", 10))
	logger.Info("我是Info級(jí)別的日志", zap.String("key", "value"), zap.Int("num", 10))
	logger.Error("我是Error級(jí)別的日志", zap.String("key", "value"), zap.Int("num", 10))
	logger.Panic("我是Panic級(jí)別的日志", zap.String("key", "value"), zap.Int("num", 10))
}

NewExample/NewDevelopment/NewProduction使用

zap 為我們提供了三種快速創(chuàng)建 logger 的方法: zap.NewProduction()zap.NewDevelopment(),zap.NewExample()

Example 一般用在測(cè)試代碼中,Development 用在開(kāi)發(fā)環(huán)境中,Production 用在生成環(huán)境中

NewExample()使用

NewExample 構(gòu)建一個(gè) logger,專(zhuān)門(mén)為在 zap 的測(cè)試示例使用。它將 DebugLevel 及以上日志用 JSON 格式標(biāo)準(zhǔn)輸出,但它省略了時(shí)間戳和調(diào)用函數(shù),以保持示例輸出的簡(jiǎn)短和確定性

因?yàn)樵谶@個(gè)方法里,zap 已經(jīng)定義好了日志配置項(xiàng)部分默認(rèn)值

// https://github.com/uber-go/zap/blob/v1.24.0/logger.go#L127
func NewExample(options ...Option) *Logger {
	encoderCfg := zapcore.EncoderConfig{
        MessageKey:     "msg",  // 日志內(nèi)容key:val, 前面的key設(shè)為msg
		LevelKey:       "level", // 日志級(jí)別的key設(shè)為level
		NameKey:        "logger", // 日志名
		EncodeLevel:    zapcore.LowercaseLevelEncoder, //日志級(jí)別,默認(rèn)小寫(xiě)
		EncodeTime:     zapcore.ISO8601TimeEncoder, // 日志時(shí)間
		EncodeDuration: zapcore.StringDurationEncoder,
	}
	core := zapcore.NewCore(zapcore.NewJSONEncoder(encoderCfg), os.Stdout, DebugLevel)
	return New(core).WithOptions(options...)
}

使用例子

package main
import (
	"go.uber.org/zap"
)
func main() {
	logger := zap.NewExample()
	logger.Debug("this is debug message")
}

NewDevelopment()使用

NewDevelopment() 構(gòu)建一個(gè)開(kāi)發(fā)使用的 Logger

使用例子

package main

import (
	"time"

	"go.uber.org/zap"
)
func main() {
	logger, _ := zap.NewDevelopment()
	defer logger.Sync()

	logger.Info("failed to fetch url",
		// 強(qiáng)類(lèi)型字段
		zap.String("url", "http://example.com"),
		zap.Int("attempt", 3),
		zap.Duration("duration", time.Second),
	)

	logger.With(
		// 強(qiáng)類(lèi)型字段
		zap.String("url", "http://development.com"),
		zap.Int("attempt", 4),
		zap.Duration("duration", time.Second*5),
	).Info("[With] failed to fetch url")
}

NewProduction()使用

NewProduction() 構(gòu)建了一個(gè)合理的 Prouction 日志記錄器,它將 info 及以上的日志內(nèi)容以 JSON 格式記寫(xiě)入標(biāo)準(zhǔn)錯(cuò)誤里

使用例子

package main

import (
	"time"

	"go.uber.org/zap"
)

func main() {
	logger, _ := zap.NewProduction()
	defer logger.Sync()

	url := "http://zap.uber.io"
	sugar := logger.Sugar()
	sugar.Infow("failed to fetch URL",
		"url", url,
		"attempt", 3,
		"time", time.Second,
	)

	sugar.Infof("Failed to fetch URL: %s", url)

	// 或更簡(jiǎn)潔 Sugar() 使用
	// sugar := zap.NewProduction().Sugar()
	// defer sugar.Sync()
}

傳入配置項(xiàng)

在這 3 個(gè)函數(shù)中,可以傳入一些配置項(xiàng)。

func NewExample(options …Option) *Logger

package main

import (
	"os"

	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

func main() {

	encoder := zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())

	file, _ := os.OpenFile("./log.txt", os.O_CREATE|os.O_APPEND|os.O_RDWR, os.ModePerm)
	syncFile := zapcore.AddSync(file)
	syncConsole := zapcore.AddSync(os.Stderr)
	sync := zapcore.NewMultiWriteSyncer(syncConsole, syncFile)

	core := zapcore.NewCore(encoder, sync, zapcore.InfoLevel)
	logger := zap.New(core)
	logger.Info("info 日志", zap.Int("line", 1))
}

zap.Hook() 添加回調(diào)函數(shù)

Hook (鉤子函數(shù))回調(diào)函數(shù)為用戶提供一種簡(jiǎn)單方法,在每次日志內(nèi)容記錄后運(yùn)行這個(gè)回調(diào)函數(shù),執(zhí)行用戶需要的操作。也就是說(shuō)記錄完日志后你還想做其它事情就可以調(diào)用這個(gè)函數(shù)

package main

import (
	"fmt"

	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

func main() {
	logger := zap.NewExample(zap.Hooks(func(entry zapcore.Entry) error {
		fmt.Println("[zap.Hooks]test Hooks")
		return nil
	}))
	defer logger.Sync()

	logger.Info("test output")

	logger.Warn("warn info")
}

自定義配置項(xiàng)

快速構(gòu)建 logger 日志記錄器最簡(jiǎn)單的方法就是用 zap 預(yù)定義了配置的方法:NewExample(), NewProduction() 和NewDevelopment(),這 3 個(gè)方法通過(guò)單個(gè)函數(shù)調(diào)用就可以構(gòu)建一個(gè)日志計(jì)記錄器,也可以簡(jiǎn)單配置

Config 配置項(xiàng)源碼

// zap v1.24.0
type Config struct {
    // 動(dòng)態(tài)改變?nèi)罩炯?jí)別,在運(yùn)行時(shí)你可以安全改變?nèi)罩炯?jí)別
	Level AtomicLevel `json:"level" yaml:"level"`
    // 將日志記錄器設(shè)置為開(kāi)發(fā)模式,在 WarnLevel 及以上級(jí)別日志會(huì)包含堆棧跟蹤信息
	Development bool `json:"development" yaml:"development"`
    // 在日志中停止調(diào)用函數(shù)所在文件名、行數(shù)
	DisableCaller bool `json:"disableCaller" yaml:"disableCaller"`
    // 完全禁止自動(dòng)堆棧跟蹤。默認(rèn)情況下,在 development 中,warnlevel及以上日志級(jí)別會(huì)自動(dòng)捕獲堆棧跟蹤信息
    // 在 production 中,ErrorLevel 及以上也會(huì)自動(dòng)捕獲堆棧信息
	DisableStacktrace bool `json:"disableStacktrace" yaml:"disableStacktrace"`
    // 設(shè)置采樣策略。沒(méi)有 SamplingConfing 將禁止采樣
	Sampling *SamplingConfig `json:"sampling" yaml:"sampling"`
    // 設(shè)置日志編碼??梢栽O(shè)置為 console 和 json。也可以通過(guò) RegisterEncoder 設(shè)置第三方編碼格式
	Encoding string `json:"encoding" yaml:"encoding"`
    // 為encoder編碼器設(shè)置選項(xiàng)。詳細(xì)設(shè)置信息在 zapcore.zapcore.EncoderConfig
	EncoderConfig zapcore.EncoderConfig `json:"encoderConfig" yaml:"encoderConfig"`
    // 日志輸出地址可以是一個(gè) URLs 地址或文件路徑,可以設(shè)置多個(gè)
	OutputPaths []string `json:"outputPaths" yaml:"outputPaths"`
    // 錯(cuò)誤日志輸出地址。默認(rèn)輸出標(biāo)準(zhǔn)錯(cuò)誤信息
	ErrorOutputPaths []string `json:"errorOutputPaths" yaml:"errorOutputPaths"`
    // 可以添加自定義的字段信息到 root logger 中。也就是每條日志都會(huì)攜帶這些字段信息,公共字段
	InitialFields map[string]interface{} `json:"initialFields" yaml:"initialFields"`
}

EncoderConfig 結(jié)構(gòu)源碼,它里面也有很多配置選項(xiàng)

// zapcore@v1.24.0
type EncoderConfig struct {
    // 為log entry設(shè)置key。如果 key 為空,那么在日志中的這部分信息也會(huì)省略
	MessageKey     string `json:"messageKey" yaml:"messageKey"`//日志信息的健名,默認(rèn)為msg
	LevelKey       string `json:"levelKey" yaml:"levelKey"`//日志級(jí)別的健名,默認(rèn)為level
	TimeKey        string `json:"timeKey" yaml:"timeKey"`//記錄日志時(shí)間的健名,默認(rèn)為time
	NameKey        string `json:"nameKey" yaml:"nameKey"`
	CallerKey      string `json:"callerKey" yaml:"callerKey"`
	FunctionKey    string `json:"functionKey" yaml:"functionKey"`
	StacktraceKey  string `json:"stacktraceKey" yaml:"stacktraceKey"`
	SkipLineEnding bool   `json:"skipLineEnding" yaml:"skipLineEnding"`
	LineEnding     string `json:"lineEnding" yaml:"lineEnding"`
    // 日志編碼的一些設(shè)置項(xiàng)
	EncodeLevel    LevelEncoder    `json:"levelEncoder" yaml:"levelEncoder"`
	EncodeTime     TimeEncoder     `json:"timeEncoder" yaml:"timeEncoder"`
	EncodeDuration DurationEncoder `json:"durationEncoder" yaml:"durationEncoder"`
	EncodeCaller   CallerEncoder   `json:"callerEncoder" yaml:"callerEncoder"`
    // 與其它編碼器不同, 這個(gè)編碼器可選
	EncodeName NameEncoder `json:"nameEncoder" yaml:"nameEncoder"`
    // 配置 interface{} 類(lèi)型編碼器。如果沒(méi)設(shè)置,將用 json.Encoder 進(jìn)行編碼
	NewReflectedEncoder func(io.Writer) ReflectedEncoder `json:"-" yaml:"-"`
    // 配置 console 中字段分隔符。默認(rèn)使用 tab 
	ConsoleSeparator string `json:"consoleSeparator" yaml:"consoleSeparator"`
}
type Entry struct {
	Level      Level
	Time       time.Time
	LoggerName string
	Message    string
	Caller     EntryCaller
	Stack      string
}zap 提供了 2 種日志記錄器:`SugaredLogger` 和 `Logger`

SugaredLogger日志

在需要性能但不是很重要的情況下,使用 SugaredLogger 較合適。它比其它結(jié)構(gòu)化日志包快 4-10 倍,包括 結(jié)構(gòu)化日志和 printf 風(fēng)格的 API

使用: suger.[日志級(jí)別]x

w 支持鍵值對(duì)方式傳入日志

f 支持%s 方式插值

suger := logger.Sugar() // 開(kāi)啟日志語(yǔ)法糖
    
suger.Debug("我是Debug級(jí)別的日志")
suger.Debugw("我是Debug級(jí)別的日志", "key1", "value1", "key2", "value2")
suger.Debugf("我是Debug級(jí)別的日志%s", "value")

suger.Warn("我是Warn級(jí)別的日志")
suger.Warnw("我是Warn級(jí)別的日志", "key1", "value1", "key2", "value2")
suger.Warnf("我是Warn級(jí)別的日志%s", "value")

suger.Info("我是Info級(jí)別的日志")
suger.Infow("我是Info級(jí)別的日志", "key1", "value1", "key2", "value2")
suger.Infof("我是Info級(jí)別的日志 %s", "info")

suger.Error("我是Error級(jí)別的日志")
suger.Errorw("我是Error級(jí)別的日志", "key1", "value1", "key2", "value2")
suger.Errorf("我是Error級(jí)別的日志 %s", "value")

suger.Panic("我是Panic級(jí)別的日志")
suger.Panicw("我是Panic級(jí)別的日志", "key1", "value1", "key2", "value2")
suger.Panicf("我是Panic級(jí)別的日志 %s", "value")

logger日志

當(dāng)性能和類(lèi)型安全很重要時(shí),請(qǐng)使用 Logger。它比 SugaredLogger 更快,分配的資源更少,但它只支持結(jié)構(gòu)化日志和強(qiáng)類(lèi)型字段

第一個(gè)參數(shù)打印日志名,后邊支持傳入可變參, …zapcore.Field類(lèi)型

zap.String(“key”, “value”) 表示打印key為字符串 value為字符串的 map

logger.Debug("我是Debug級(jí)別的日志", zap.String("key", "value"), zap.Int("num", 10))
logger.Warn("我是Warn級(jí)別的日志", zap.String("key", "value"), zap.Int("num", 10))
logger.Info("我是Info級(jí)別的日志", zap.String("key", "value"), zap.Int("num", 10))
logger.Error("我是Error級(jí)別的日志", zap.String("key", "value"), zap.Int("num", 10))
logger.Panic("我是Panic級(jí)別的日志", zap.String("key", "value"), zap.Int("num", 10))

輸出日志到文件

cfg := zap.NewProductionConfig()
cfg.OutputPaths = []string{
   "./OUTPUT.log", "stderr", "stdout",
}
logger, _ = cfg.Build()

logger.Debug("打印日志到文件")

日志切割歸檔

lumberjack 這個(gè)庫(kù)是按照日志大小切割日志文件。

go get -u github.com/natefinch/lumberjack@v2
log.SetOutput(&lumberjack.Logger{
    Filename:   "/var/log/myapp/foo.log", // 文件位置
    MaxSize:    500,  // megabytes,M 為單位,達(dá)到這個(gè)設(shè)置數(shù)后就進(jìn)行日志切割
    MaxBackups: 3,    // 保留舊文件最大份數(shù)
    MaxAge:     28,   //days , 舊文件最大保存天數(shù)
    Compress:   true, // disabled by default,是否壓縮日志歸檔,默認(rèn)不壓縮
})

參照它的文檔和結(jié)合上面自定義配置

package main

import (
	"fmt"

	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"gopkg.in/natefinch/lumberjack.v2"
)

func main() {
	lumberjacklogger := &lumberjack.Logger{
		Filename:   "./log-rotate-test.json",
		MaxSize:    1, // megabytes
		MaxBackups: 3,
		MaxAge:     28,   //days
		Compress:   true, // disabled by default
	}
	defer lumberjacklogger.Close()

	config := zap.NewProductionEncoderConfig()

	config.EncodeTime = zapcore.ISO8601TimeEncoder // 設(shè)置時(shí)間格式
	fileEncoder := zapcore.NewJSONEncoder(config)

	core := zapcore.NewCore(
		fileEncoder,                       //編碼設(shè)置
		zapcore.AddSync(lumberjacklogger), //輸出到文件
		zap.InfoLevel,                     //日志等級(jí)
	)

	logger := zap.New(core)
	defer logger.Sync()

    // 測(cè)試分割日志
	for i := 0; i < 8000; i++ {
		logger.With(
			zap.String("url", fmt.Sprintf("www.test%d.com", i)),
			zap.String("name", "jimmmyr"),
			zap.Int("age", 23),
			zap.String("agradege", "no111-000222"),
		).Info("test info ")
	}

}

全局logger

zap提供了 2 種全局 Logger,一個(gè)是 zap.Logger,調(diào)用 zap.L() 獲?。?br />另外一個(gè)是 zap.SugaredLogger ,調(diào)用 zap.S() 獲取

直接調(diào)用 zap.L() 或 zap.S() 記錄日志的話,它是不會(huì)記錄任何日志信息。需要調(diào)用 ReplaceGlobals() 函數(shù)將它設(shè)置為全局 Logger。
ReplaceGlobals 替換全局 Logger 和 SugaredLogger,并返回一個(gè)函數(shù)來(lái)恢復(fù)原始值

簡(jiǎn)單使用例子

package main

import (
	"go.uber.org/zap"
)

func main() {
	// 直接調(diào)用是不會(huì)記錄日志信息的,所以下面日志信息不會(huì)輸出
	zap.L().Info("no log info")
	zap.S().Info("no log info [sugared]")

	logger := zap.NewExample()
	defer logger.Sync()

	zap.ReplaceGlobals(logger) // 全局logger,zap.L() 和 zap.S() 需要調(diào)用 ReplaceGlobals 函數(shù)才會(huì)記錄日志信息
	zap.L().Info("log info")
	zap.S().Info("log info [sugared]")
}

到此這篇關(guān)于golang整合日志zap的實(shí)現(xiàn)示例的文章就介紹到這了,更多相關(guān)golang整合日志zap內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家! 

相關(guān)文章

  • Go基礎(chǔ)Slice教程詳解

    Go基礎(chǔ)Slice教程詳解

    這篇文章主要介紹了Go基礎(chǔ)Slice教程詳解,需要的朋友可以參考下
    2018-02-02
  • Golang中的深拷貝與淺拷貝使用

    Golang中的深拷貝與淺拷貝使用

    本文主要介紹了Golang中的深拷貝與淺拷貝使用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-04-04
  • 詳解如何在Go項(xiàng)目中輸出版本信息

    詳解如何在Go項(xiàng)目中輸出版本信息

    這篇文章主要介紹了詳解如何在Go項(xiàng)目中輸出版本信息,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-01-01
  • 深入解析Go語(yǔ)言中crypto/subtle加密庫(kù)

    深入解析Go語(yǔ)言中crypto/subtle加密庫(kù)

    本文主要介紹了深入解析Go語(yǔ)言中crypto/subtle加密庫(kù),詳細(xì)介紹crypto/subtle加密庫(kù)主要函數(shù)的用途、工作原理及實(shí)際應(yīng)用,具有一定的參考價(jià)值,感興趣的可以了解一下
    2024-02-02
  • golang flag簡(jiǎn)單用法

    golang flag簡(jiǎn)單用法

    本篇文章介紹了golang flag包的一個(gè)簡(jiǎn)單的用法,希望通過(guò)一個(gè)簡(jiǎn)單的實(shí)例,能讓大家了解它的用法,從中獲得啟發(fā)
    2018-09-09
  • golang開(kāi)發(fā)安裝go-torch火焰圖操作步驟

    golang開(kāi)發(fā)安裝go-torch火焰圖操作步驟

    這篇文章主要為大家介紹了golang開(kāi)發(fā)安裝go-torch火焰圖操作步驟
    2021-11-11
  • golang字符串拼接實(shí)現(xiàn)方式和區(qū)別對(duì)比

    golang字符串拼接實(shí)現(xiàn)方式和區(qū)別對(duì)比

    本文介紹了Go語(yǔ)言中字符串拼接的多種方法及其優(yōu)缺點(diǎn),推薦使用strings.Builder進(jìn)行頻繁拼接以優(yōu)化內(nèi)存分配和性能,同時(shí),還討論了通過(guò)sync.Pool優(yōu)化高頻創(chuàng)建的對(duì)象,以減少垃圾回收壓力,感興趣的朋友一起看看吧
    2025-02-02
  • Golang控制協(xié)程執(zhí)行順序方法詳解

    Golang控制協(xié)程執(zhí)行順序方法詳解

    這篇文章主要介紹了Golang控制協(xié)程執(zhí)行順序的方法,Golang的語(yǔ)法和運(yùn)行時(shí)直接內(nèi)置了對(duì)并發(fā)的支持。Golang里的并發(fā)指的是能讓某個(gè)函數(shù)獨(dú)立于其他函數(shù)運(yùn)行的能力
    2022-11-11
  • GO語(yǔ)言make()分配用法實(shí)例

    GO語(yǔ)言make()分配用法實(shí)例

    這篇文章主要介紹了GO語(yǔ)言make()分配用法,實(shí)例分析了make()的功能及使用技巧,需要的朋友可以參考下
    2015-02-02
  • 一文帶你深入理解Golang中的RWMutex

    一文帶你深入理解Golang中的RWMutex

    這篇文章主要為大家詳細(xì)介紹了Golang中RWMutex的相關(guān)知識(shí),知其然,更要知其所以然。文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下
    2023-04-04

最新評(píng)論