首页 > 其他分享 >go中实现日志级别与切割,日志配置热生效,pprof的技术解析

go中实现日志级别与切割,日志配置热生效,pprof的技术解析

时间:2024-07-02 11:55:45浏览次数:3  
标签:return err pprof fmt funcMaster json go 日志

引言

在线上分布式系统和微服务架构中,日志记录是排查问题、调试程序和监控服务运行状态的重要手段。合理设置日志级别,可以帮助开发和运维人员有效地获取所需信息。然而,在实际运行中,常常需要在不重启服务的情况下动态调整日志级别,以适应不同的调试需求和运行环境。本文基于go项目将介绍如何在不重启 funcMaster 服务的情况下,实现日志级别的动态切换。

实现方案

1. 需求分析

目标是在 funcMaster 服务中实现日志级别的动态调整,即不需要重启服务即可改变日志输出的详细程度。为此,需要实现以下功能:

  1. 配置文件读取:服务启动时读取配置文件,获取初始日志级别。
  2. 日志管理:实现一个灵活的日志管理器,根据配置文件中的日志级别动态调整日志输出。
  3. 配置更新:通过外部工具发送新的配置,并在服务中接收和应用该配置。

2. 主要技术点

为了实现上述功能,主要使用以下技术和方法:

  • TCP 通信:服务通过 TCP 监听配置更新请求。
  • 动态调整日志级别:日志管理器根据新的配置动态更新日志级别。
  • 热加载配置:外部工具发送新的配置,服务接收到后立即应用。

3. 代码实现

3.1 主服务 funcMaster.go

主服务负责读取初始配置、启动日志管理器、监听配置更新请求,并根据新的配置动态调整日志级别。

package main

import (
    // Import necessary packages
    _ "net/http/pprof" 
  	_ "github.com/mkevac/debugcharts"
)

// 配置信息结构体
type SotConfig struct {
    // 定义服务的各项配置参数
    LogParam LogParam `json:"logParam"`
}

// 日志参数结构体
type LogParam struct {
    LogLevelStr string `json:"logLevel"`
    LogLevel    int
    LogPath     string `json:"logPath"`
    MaxSize     int    `json:"maxSize"`
    MaxBackups  int    `json:"maxBackups"`
    MaxAge      int    `json:"maxAge"`
}

// 服务主结构体
type FuncMaster struct {
    sotConfig SotConfig
    log       *UULogger
}

// 主函数
func main() {
    cmd := ""
    if len(os.Args) > 1 {
        cmd = os.Args[1]
    }

    if cmd == "start" {
        path := os.Args[2]
        status := startSotByPath(path)
        fmt.Println(status)
    } else {
        info := `
funcMaster is a module for tunneling A datagrams over a B stream.
Usage:
    funcMaster <command> [arguments]
The commands are:
    start       start funcMaster module by config path, return: [0,1,2,3]. 
`
        fmt.Println(info)
    }
}

// 读取配置文件并启动服务
func startSotByPath(configPath string) int32 {
    configData, err := ioutil.ReadFile(configPath)
    if err != nil {
        fmt.Println("error reading configPath")
        return -1
    }
    return startSot(string(configData))
}

// 解析配置并启动服务
func startSot(config string) int32 {
    var sotConfig SotConfig
    err := json.Unmarshal([]byte(config), &sotConfig)
    if err != nil {
        fmt.Printf("SotConfig parse error:%v\n", err)
        return Start_Error_Config
    }

    // 初始化日志
    var logger *UULogger
    logger = NewFileLogger(&sotConfig.LogParam, fmt.Sprintf("(%s) ", sotConfig.LogParam.LogLevelStr))

    // debug,启动pprof工具
    if sotConfig.LogParam.LogLevelStr == "debug" {
        logger.startPprof()
    }
    //清理单例
	clearSingleSot()
    // 启动配置服务器
    server := getSingleSot(sotConfig, logger)
    go startConfigServer(server)

    // 启动server服务
    ret := server.start()
    if ret == 0 {
        logger.Info("server已启动 ret:0\n")
    } else {
        logger.Info("server失败,错误码:%d\n", ret)
    }
    return ret
}

var singleInstance *FuncMaster 
// 单例构建锁
var mu sync.Mutex
// 清理单例
func clearSingleSot() {
	mu.Lock()
	if singleInstance != nil {
		singleInstance.stop()
		singleInstance = nil
	}
	mu.Unlock()
}

func getSingleSot(sotConfig SotConfig, logger *UULogger) *FuncMaster {
	if singleSotInstance == nil {
		singleSotInstance = &FuncMaster {sotConfig: sotConfig, log: logger}
	}
	return singleSotInstance
}

// 配置服务器监听
func startConfigServer(server *FuncMaster) {
    listener, err := net.Listen("tcp", "127.0.0.1:7777")
    if err != nil {
        fmt.Println("Error starting config server:", err)
        return
    }
    defer listener.Close()

    for {
        conn, err := listener.Accept()
        if err != nil {
            fmt.Println("Error accepting connection:", err)
            continue
        }
        go handleConfigConnection(conn, server)
    }
}

// 处理配置更新请求
func handleConfigConnection(conn net.Conn, server *FuncMaster ) {
    defer conn.Close()
    var newConfig SotConfig
    err := json.NewDecoder(conn).Decode(&newConfig)
    if err != nil {
        fmt.Println("Error decoding config:", err)
        return
    }

    server.sotConfig.LogParam = newConfig.LogParam
    server.log.SetLogLevel(newConfig.LogParam.LogLevelStr)
    response := fmt.Sprintf("Log level updated to %s successfully", newConfig.LogParam.LogLevelStr)
    err = json.NewEncoder(conn).Encode(response)
    if err != nil {
        fmt.Println("Error encoding response:", err)
        return
    }
}

3.2 日志管理器 logger.go

日志管理器负责日志记录,并提供动态调整日志级别的功能。

package main

import (
    "fmt"
    "io"
    "log"
    "net/http"
    "os"
    "strings"
    "time"

    "gopkg.in/natefinch/lumberjack.v2"
)

const (
    DEBUG = iota
    INFO
    WARN
    ERROR
    FATAL
)

const (
    ENV_LOG_FILE = "/var/log/funcMaster.log"
)

type UULogger struct {
    logger      *log.Logger
    logLevel    int
    logLevelStr string
    prepend     string
    pprofServer *http.Server
}

func NewFileLogger(logParam *LogParam, prepend string) *UULogger {
    logFileName := logParam.LogPath

    if logFileName == "" {
        logFileName = ENV_LOG_FILE
        fmt.Printf("Default logs path:%s\n", ENV_LOG_FILE)
    } else {
        fileDir, err := os.Stat(logFileName)
        if err == nil && fileDir.IsDir() {
            logFileName = ENV_LOG_FILE
            fmt.Printf("Default logs path:%s\n", ENV_LOG_FILE)
        }
    }

    infoLumberIO := &lumberjack.Logger{
        Filename:   logFileName,
        MaxSize:    logParam.MaxSize,
        MaxBackups: logParam.MaxBackups,
        MaxAge:     logParam.MaxAge,
        LocalTime:  true,
        Compress:   true,
    }

    var uulog UULogger
    logger := log.New(io.MultiWriter(infoLumberIO), "["+logParam.LogLevelStr+"] "+prepend, log.Ldate|log.Ltime)
    uulog.logger = logger
    uulog.prepend = prepend
    uulog.logLevel = logParam.LogLevel
    uulog.logLevelStr = logParam.LogLevelStr
    return &uulog
}

func (log *UULogger) Debug(format string, v ...interface{}) {
    if log.logLevel <= DEBUG {
        log.logger.SetPrefix("[debug] " + log.prepend)
        log.logger.Printf(format, v...)
    }
}

func (log *UULogger) Info(format string, v ...interface{}) {
    if log.logLevel <= INFO {
        log.logger.SetPrefix("[info] " + log.prepend)
        log.logger.Printf(format, v...)
    }
}

func (log *UULogger) Warning(format string, v ...interface{}) {
    if log.logLevel <= WARN {
        log.logger.SetPrefix("[warn] " + log.prepend)
        log.logger.Printf(format, v...)
    }
}

func (log *UULogger) Error(format string, v ...interface{}) {
    if log.logLevel <= ERROR {
        log.logger.SetPrefix("[error] " + log.prepend)
        log.logger.Printf(format, v...)
    }
}

func (log *UULogger) Fatal(format string, v ...interface{}) {
    if log.logLevel <= FATAL {
        log.logger.SetPrefix("[fatal] " + log.prepend)
        log.logger.Printf(format, v...)
    }
}
//启动性能跟踪工具pprof服务
func (log *UULogger) startPprof() {
    if log.pprofServer == nil {
        addr := "0.0.0.0:6060"
        log.pprofServer = &http.Server{Addr: addr}
        go func() {
            log.Debug("pprof 服务器启动,地址:%s", addr)
            err := log.pprofServer.ListenAndServe()
            if err != nil && err != http.ErrServerClosed {
                log.Debug("pprof 服务器启动失败 %v", err)
            }
        }()
    }
}

//停止性能跟踪工具pprof服务
func (log *UULogger) stopPprof() {
    if log.pprofServer != nil {
        log.Debug("pprof 服务器停止")
        err := log.pprofServer.Close()
        if err != nil {
            log.Debug("pprof 服务器停止失败 %v", err)
        }
        log.pprofServer = nil
    }
}

func (log *UULogger) SetLogLevel(leverStr string) {
    log.logLevelStr = strings.ToUpper(leverStr)
    switch log.logLevelStr {
    case "DEBUG":
        log.logLevel = DEBUG
        log.startPprof()
    case "INFO":
        log.logLevel = INFO
        log.stopPprof()
    case "WARN":
        log.logLevel = WARN
        log.stopPprof()
    case "ERROR":
        log.logLevel = ERROR
        log.stopPprof()
    case "FATAL":
        log.logLevel = FATAL
        log.stopPprof()
    default:
        log.logLevel = INFO
        log.stopPprof()
    }

    log.logger.SetPrefix("[" + log.logLevelStr + "] " + log.prepend)
    log.logger.Output(2, "Log level updated to "+log.logLevelStr)
}

3.3 配置更新工具 updateConfig.go

配置更新工具负责读取新的配置文件,通过 TCP 发送给主服务。

package main

import (
    "encoding/json"
    "fmt"
    "net"
    "os"
)

type NewLogParam struct {
    LogLevelStr string `json:"logLevel"`
    LogLevel    int
    LogPath     string `json:"logPath"`
    MaxSize     int    `json:"maxSize"`
    MaxBackups  int    `json:"maxBackups"`
    MaxAge      int    `json:"maxAge"`
}

type NewSotConfig struct {
    LogParam NewLogParam `json:"logParam"`
}

// 验证日志级别是否有效
func isValidLogLevel(logLevel string) bool {
    validLogLevels := []string{"DEBUG", "INFO", "WARN", "ERROR", "FATAL", "debug", "info", "warn", "error", "fatal"}
    for _, validLevel := range validLogLevels {
        if logLevel == validLevel {
            return true
        }
    }
    return false
}

// go build -o updateConfig updateConfig.go
func main() {
    if len(os.Args) < 2 {
        fmt.Println("Usage: updateConfig <configFilePath>")
        return
    }

    configFilePath := os.Args[1]
    configData, err := os.ReadFile(configFilePath)
    if err != nil {
        fmt.Printf("Error reading config file: %v\n", err)
        return
    }

    var newConfig NewSotConfig
    err = json.Unmarshal(configData, &newConfig)
    if err != nil {
        fmt.Printf("Error parsing config file: %v\n", err)
        return
    }

    // 检查日志级别是否有效
    if !isValidLogLevel(newConfig.LogParam.LogLevelStr) {
        fmt.Printf("Invalid log level: %s. Valid log levels are: debug, info, warn, error, fatal, DEBUG, INFO, WARN, ERROR, FATAL\n", newConfig.LogParam.LogLevelStr)
        return
    }

    conn, err := net.Dial("tcp", "127.0.0.1:7777")
    if err != nil {
        fmt.Printf("Error connecting to server: %v\n", err)
        return
    }
    defer conn.Close()

    err = json.NewEncoder(conn).Encode(newConfig)
    if err != nil {
        fmt.Printf("Error sending config: %v\n", err)
        return
    }

    var response string
    err = json.NewDecoder(conn).Decode(&response)
    if err != nil {
        fmt.Printf("Error receiving response: %v\n", err)
        return
    }

    fmt.Println("Response from server:", response)
}

4. 使用说明

4.1 编译项目
go build -o funcMaster funcMaster.go logger.go
go build -o updateConfig updateConfig.go

4.2 运行主服务

创建配置文件 funcMaster.config:

{
    "logParam": {
        "logLevel": "info",
        "logPath": "/var/log/funcMaster.log",
        "maxSize": 100,
        "maxBackups": 30,
        "maxAge": 30
    }
}

启动主服务:

./funcMaster start funcMaster.config

4.3 更新日志配置

编辑配置文件 funcMaster.config,修改 logLevel 字段,例如改为 debug:

./updateConfig funcMaster.config

如果配置正确,输出将显示:

Response from server: Log level updated to debug successfully

5. 总结

通过以上技术实现,可以在不重启服务的情况下,动态调整 funcMaster 服务的日志级别。
1. 提供了日志级别组件,包含第三方组件 Lumberjack 实现日志切割
2. debug下提供go性能工具pprof服务
3. 日志级别设置热生效

这不仅提高了调试的灵活性,还减少了服务中断的风险。
希望这篇文章能对您有所帮助,在您的项目中也能实现类似的功能。

标签:return,err,pprof,fmt,funcMaster,json,go,日志
From: https://blog.csdn.net/weixin_41578517/article/details/140121776

相关文章

  • 图神经网络版本的Kolmogorov Arnold(KAN)代码实现和效果对比
    KolmogorovArnoldNetworks(KAN)最近作为MLP的替代而流行起来,KANs使用Kolmogorov-Arnold表示定理的属性,该定理允许神经网络的激活函数在边缘上执行,这使得激活函数“可学习”并改进它们。目前我们看到有很多使用KAN替代MLP的实验,但是目前来说对于图神经网络来说还没有类似的实验......
  • 【Springboot】基于AOP实现操作日志记录
    基于AOP实现操作日志记录文章目录基于AOP实现操作日志记录前言一、AOP1.介绍2.AOP核心概念二、基于AOP实现操作日志记录1.准备工作2.创建自定义注解和切面类3.实现日志记录总结前言 在springboot项目中,往往需要在用户完成某些操作(例如:增,删,改)时,能够将相关操作信......
  • 云原生周刊:Argo Rollouts 支持 Kubernetes Gateway API 1.0 | 2024.7.1
    开源项目KubetoolsRecommenderSystemKubetoolsRecommenderSystem(Krs)是一个基于GenAI的工具,用于帮助管理和优化Kubernetes集群。buoybuoy是Kubernetes的声明式TUI仪表板。你可以在JSON文件中定义仪表板,它将从Kubernetes集群中获取信息并构建仪表板,以便在......
  • Godot游戏学习笔记(二)
    Godot学习笔记(二)前言今天这部分是想做一个基本的背包系统,既可以存放基本的物品。一、个人思路我认为的背包系统中主要有三项基本的物品、用于存放物品的单位和背包页面这三个部分,其中第二个部分用于存放物品的单位可以不用单独构建,但是构建了会更方便管理,所以我加上了第......
  • 最新扣子(Coze)实战案例:图像流工具之创建一个精美的LOGO,完全免费教程
    ......
  • day33-Django3.2(二)
    四、视图django的视图主要有2种,分别是函数视图和类视图.现在刚开始学习django,我们先学习函数视图(FBV),后面再学习类视图[CBV].4.1、请求方式web项目运行在http协议下,默认肯定也支持用户通过不同的http请求发送数据来。django支持让客户端只能通过指定的Http请求来访问到项......
  • day13 Goroutine 协程
    了解计算机原理进程:计算机资源分配单位线程:cpu处理单位协程:以特殊机制或者函数实现高并发,又称轻量级线程了解GoroutineGoGoroutine,go语言中的协程,实现并发。关键字go初始大小4k,随着程序执行自动增长和删除实现多线程并发执行packagemainimport"fmt"fu......
  • Django之文件上传
    前端 <!DOCTYPEhtml><html><body><h2>UploadFile</h2><formaction="http://127.0.0.1:5000/upload"method="post"enctype="multipart/form-data">Selectfiletoupload:<inputtype=&q......
  • 6、Django-管理员界面-admin
    概念:Django的admin界面是自动生成的,它根据你的模型类自动创建表单和列表视图。你只需将模型类注册到admin界面,就可以轻松地管理和操作数据库中的数据。admin界面提供了各种功能,包括:列表视图:以表格形式展示数据库中的数据,支持分页、搜索和排序功能,方便快速浏览和筛选数据。表......
  • 5、Django-模型-models
    概念:基本模板:模板其实就是我们使用的HTML写好的页面--先在应用的目录下创建模板文件夹templates、然后在templates中创建模板文件、如html--最后在views.py中去渲染模板、使用render函数将模板返回给用户:returnrender(request,'.html')  定义模板:--模板里就是要对......