WebP Cloud Services Blog

我们是怎么在运行时修改 Go 程序的 LogLevel 的

· Benny Think

前几天,我们偶然发现,我们的 WebPLB 日志被一个来自于我们自己的控制请求刷屏了。

WebPLB 是我们实际接受流量的程序的名字,我们 API Service 的名字叫做 WebPPT,猜猜看我们别的服务叫什么?(提示:他们都符合 WebP[A-Z][A-Z] 的格式

日志太多,会给线上的除错带来很多麻烦。除了把对应的不重要的日志改为 Debug,我们也在想,如何在运行时改变 logLevel?这样有需要的时候我们就可以改变日志级别,应用程序不用重启即可看到需要的日志。

我们的 log 使用的是 logrus,因此可以通过如下代码轻松改变 log level

log.SetLevel(log.DebugLevel)

但问题是如何在运行时调用这段代码呢?一个最朴素的想法是环境变量,但是如果用环境变量就需要考虑如下问题:

  1. 应用程序需要定期跑一个任务,来读取环境变量并更新日志级别,因此会有一定时间的延迟
  2. 环境变量的生效问题。当前 shell 设置的环境变量,对于已经运行的进程并不可见。

突然想到,UNIX中有一个非常重要的概念—— 信号。信号是一种进程间通信的方法,当进程接收到一个信号时,可以选择处理并执行。这恰巧就是我们的需求,并且使用信号有如下优点:

  1. 不需要定期任务,没有延迟
  2. 方便随时调整,不需要重启
  3. 原生支持,也许连 Windows 也行

通过 kill -L 我们可以看到有哪些信号,总结如下

Signal NumberSignal NameDefault Behavior
1SIGHUPTerminate process; can be handled, ignored, or caught
2SIGINTInterrupt process (like Ctrl+C); can be caught or ignored
3SIGQUITQuit process and generate core dump
4SIGILLIllegal Instruction; generally leads to a core dump
5SIGTRAPTrap for debugging
6SIGABRTAbort signal from abort() system call
7SIGBUSBus error (bad memory access)
8SIGFPEFloating-point exception
9SIGKILLKill signal; cannot be caught, blocked, or ignored
10SIGUSR1User-defined signal 1
11SIGSEGVInvalid memory reference (segmentation fault)
12SIGUSR2User-defined signal 2
13SIGPIPEWrite to a pipe with no reader; can be caught or ignored
14SIGALRMTimer signal from alarm() system call
15SIGTERMTermination signal; can be caught or ignored
16SIGSTKFLTStack fault (not common on most Unix systems)
17SIGCHLDChild process terminated, stopped (or resumed)
18SIGCONTContinue executing, if stopped; cannot be blocked
19SIGSTOPStop process; cannot be caught, blocked, or ignored
20SIGTSTPTerminal stop signal (like Ctrl+Z)
21SIGTTINBackground process attempting read
22SIGTTOUBackground process attempting write
23SIGURGUrgent condition on socket (like out-of-band data)
24SIGXCPUCPU time limit exceeded (see setrlimit())
25SIGXFSZFile size limit exceeded (see setrlimit())
26SIGVTALRMVirtual alarm clock (from setitimer())
27SIGPROFProfiling timer expired
28SIGWINCHWindow resize signal
29SIGIOI/O now possible (from fcntl())
30SIGPWRPower failure
31SIGSYSBad system call
34-64SIGRTMIN to SIGRTMAXReal-time signals; behavior is application-specific

我们可以用 SIGUSR1 和 SIGUSR2 两个自定义信号,只需要在 main 里通过 goroutine 跑起来就好了,示例代码如下:

package main

import (
	"os"
	"os/signal"
	"syscall"
	"time"

	"github.com/sirupsen/logrus"
)

func main() {
	log := logrus.New()
	log.SetLevel(logrus.InfoLevel)

	sigChan := make(chan os.Signal, 1)
	signal.Notify(sigChan, syscall.SIGUSR1, syscall.SIGUSR2)

	go func() {
		for {
			sig := <-sigChan
			switch sig {
			case syscall.SIGUSR1:
				log.SetLevel(logrus.DebugLevel)
				log.Warnln("Log level changed to DEBUG")
			case syscall.SIGUSR2:
				log.SetLevel(logrus.InfoLevel)
				log.Warnln("Log level changed to INFO")
			}
		}
	}()

	// rest of your application
	for {
		log.Debugln("debug log")
		log.Infoln("info log")
		time.Sleep(time.Second * 2)
	}

}

另开一个窗口,通过 htop 或者 kill -s SIGUSR1 38935 发信号即可,如下,可以看到程序的 Loglevel 在运行时就被改变啦~


WebP Cloud Services 团队是一个来自上海和赫尔辛堡的三人小团队,由于我们不融资,且没有盈利压力 ,所以我们会坚持做我们认为正确的事情,力求在我们的资源和能力允许范围内尽量把事情做到最好, 同时也会在不影响对外提供的服务的情况下整更多的活,并在我们产品上实践各种新奇的东西。

如果你觉得我们的这个服务有意思或者对我们服务感兴趣,欢迎登录 WebP Cloud Dashboard 来体验,如果你好奇它还有哪些神奇的功能,可以来看看我们的文档 WebP Cloud Services Docs,希望大家玩的开心~


Discuss on Hacker News