前几天,我们偶然发现,我们的 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 在运行时就被改变啦~
