我们是怎么在运行时修改 Go 程序的 LogLevel 的
前几天,我们偶然发现,我们的 WebPLB 日志被一个来自于我们自己的控制请求刷屏了。
WebPLB 是我们实际接受流量的程序的名字,我们 API Service 的名字叫做 WebPPT,猜猜看我们别的服务叫什么?(提示:他们都符合
WebP[A-Z][A-Z]
的格式
日志太多,会给线上的除错带来很多麻烦。除了把对应的不重要的日志改为 Debug,我们也在想,如何在运行时改变 logLevel?这样有需要的时候我们就可以改变日志级别,应用程序不用重启即可看到需要的日志。
我们的 log 使用的是 logrus,因此可以通过如下代码轻松改变 log level
log.SetLevel(log.DebugLevel)
但问题是如何在运行时调用这段代码呢?一个最朴素的想法是环境变量,但是如果用环境变量就需要考虑如下问题:
- 应用程序需要定期跑一个任务,来读取环境变量并更新日志级别,因此会有一定时间的延迟
- 环境变量的生效问题。当前 shell 设置的环境变量,对于已经运行的进程并不可见。
突然想到,UNIX中有一个非常重要的概念—— 信号。信号是一种进程间通信的方法,当进程接收到一个信号时,可以选择处理并执行。这恰巧就是我们的需求,并且使用信号有如下优点:
- 不需要定期任务,没有延迟
- 方便随时调整,不需要重启
- 原生支持,也许连 Windows 也行
通过 kill -L
我们可以看到有哪些信号,总结如下
Signal Number | Signal Name | Default Behavior |
---|---|---|
1 | SIGHUP | Terminate process; can be handled, ignored, or caught |
2 | SIGINT | Interrupt process (like Ctrl+C); can be caught or ignored |
3 | SIGQUIT | Quit process and generate core dump |
4 | SIGILL | Illegal Instruction; generally leads to a core dump |
5 | SIGTRAP | Trap for debugging |
6 | SIGABRT | Abort signal from abort() system call |
7 | SIGBUS | Bus error (bad memory access) |
8 | SIGFPE | Floating-point exception |
9 | SIGKILL | Kill signal; cannot be caught, blocked, or ignored |
10 | SIGUSR1 | User-defined signal 1 |
11 | SIGSEGV | Invalid memory reference (segmentation fault) |
12 | SIGUSR2 | User-defined signal 2 |
13 | SIGPIPE | Write to a pipe with no reader; can be caught or ignored |
14 | SIGALRM | Timer signal from alarm() system call |
15 | SIGTERM | Termination signal; can be caught or ignored |
16 | SIGSTKFLT | Stack fault (not common on most Unix systems) |
17 | SIGCHLD | Child process terminated, stopped (or resumed) |
18 | SIGCONT | Continue executing, if stopped; cannot be blocked |
19 | SIGSTOP | Stop process; cannot be caught, blocked, or ignored |
20 | SIGTSTP | Terminal stop signal (like Ctrl+Z) |
21 | SIGTTIN | Background process attempting read |
22 | SIGTTOU | Background process attempting write |
23 | SIGURG | Urgent condition on socket (like out-of-band data) |
24 | SIGXCPU | CPU time limit exceeded (see setrlimit()) |
25 | SIGXFSZ | File size limit exceeded (see setrlimit()) |
26 | SIGVTALRM | Virtual alarm clock (from setitimer()) |
27 | SIGPROF | Profiling timer expired |
28 | SIGWINCH | Window resize signal |
29 | SIGIO | I/O now possible (from fcntl()) |
30 | SIGPWR | Power failure |
31 | SIGSYS | Bad system call |
34-64 | SIGRTMIN to SIGRTMAX | Real-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