解决Go升级和go agent不健壮导致启动panic问题

作者:小怪兽 发布时间: 2025-09-24 阅读量:8 评论数:0

问题描述

项目新需求依赖了github.com/crewjam/saml ---> golang.org/x/crypto, 由于toolchain机制自动将项目go版本升级到1.24,go1.24不兼容go agent v0.4(编译时报错),使用go agent v0.6编译能通过,但启动时panic。

核心是logrus format nil panic

./bin/eac --env=local
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x2 addr=0x0 pc=0x1033b7d9c]

goroutine 1 [running]:
github.com/sirupsen/logrus.skywalking_operatorStaticMethodLogrusGetLogContext(0x1)
    skywalking_logrus_context.go:82 +0x3c
github.com/sirupsen/logrus.(*skywalking_operatorTypeLogrusWrapFormat).Format(0x1400003b1a0, 0x140007b6cb0)
    skywalking_logrus_logrus_format.go:45 +0x74
github.com/sirupsen/logrus.(*Entry).write(0x140007b6cb0)
    /opt/go/gopath/pkg/mod/cache/github.com/sirupsen/logrus@v1.9.3/entry.go:289 +0xf0
github.com/sirupsen/logrus.(*Entry).log(0x14000218070, 0x4, {0x140001a725c, 0x4})
    /opt/go/gopath/pkg/mod/cache/github.com/sirupsen/logrus@v1.9.3/entry.go:252 +0x318
github.com/sirupsen/logrus.(*Entry).Log(0x14000218070, 0x4, {0x1400063fbb0, 0x1, 0x1})
    /opt/go/gopath/pkg/mod/cache/github.com/sirupsen/logrus@v1.9.3/entry.go:304 +0x88
github.com/sirupsen/logrus.(*Entry).Logf(0x14000218070, 0x4, {0x10481760e, 0x6}, {0x1400063fdf8, 0x1, 0x1})
    /opt/go/gopath/pkg/mod/cache/github.com/sirupsen/logrus@v1.9.3/entry.go:349 +0x104
github.com/sirupsen/logrus.(*Logger).Logf(0x14000220000, 0x4, {0x10481760e, 0x6}, {0x1400063fdf8, 0x1, 0x1})
    /opt/go/gopath/pkg/mod/cache/github.com/sirupsen/logrus@v1.9.3/logger.go:154 +0x74
github.com/sirupsen/logrus.(*Logger).Infof(0x14000220000, {0x10481760e, 0x6}, {0x1400063fdf8, 0x1, 0x1})
    /opt/go/gopath/pkg/mod/cache/github.com/sirupsen/logrus@v1.9.3/logger.go:168 +0x54
github.com/sirupsen/logrus.Infof({0x10481760e, 0x6}, {0x1400063fdf8, 0x1, 0x1})
    /opt/go/gopath/pkg/mod/cache/github.com/sirupsen/logrus@v1.9.3/exported.go:199 +0x50
ezone.ksyun.com/ezone/ksyun-online/enterprise-account/pkg/env.init.0()
    /Users/sairo/Project/code/enterprise-account/pkg/env/env.go:63 +0xe4

核心原因

  1. go agent在编译阶段会重写用户代码,二次包装了Logrus实现,用自定义的WrapFormat来包装基础的logrus.Formatter,可以打印SW_CTX traceId。

  2. go agent v0.6代码不够健壮,在关键位置没有对nil做判断。

  3. agent初始化逻辑依赖init执行机制,在agent自己的init尚未执行时业务代码提前调用logrus就会导致nil panic。

解决方法

方案1:init()只做具体的初始化动作,尽量不依赖三方库

init方法中不要使用logrus打印日志,可以改成fmt.Println

方案2:取消init()方法,改成显式调用

init执行顺序原则上来说是明确可预知的,但是受限于package依赖顺序,实际是不可控的,建议改成显式调用的方式:

方案3:【推荐】修改agent源码,本地重新编译

修改agent源码中这两个文件,增加nil检查

// tools/go-agent/instrument/logger/frameworks/logrus_adapt.go
func UpdateLogrusLogger(l *logrus.Logger) {
    // 添加保护性检查,防止在Go 1.24中由于init顺序变更导致的nil panic
    if l == nil {
        return
    }
    
    if LogTracingContextEnable {
        if _, wrapperd := l.Formatter.(*WrapFormat); !wrapperd {
            l.Formatter = Wrap(l.Formatter, LogTracingContextKey)
        }
    }
    
    // 确保ChangeLogger不是nil
    if ChangeLogger != nil {
        ChangeLogger(NewLogrusAdapter(l))
    }
}
// tools/go-agent/instrument/logger/context.go
func GetLogContext(withEndpoint bool) interface{} {
    operator := GetOperator()
    // 这里增加nil校验
    if operator == nil {
        return nil
    }
    
    report, ok := operator.LogReporter().(LogReporter)
    if !ok || report == nil {
        return nil
    }

    return report.GetLogContext(withEndpoint)
}

⭐⭐⭐jenkins配置调整

新的agent路径调整为:

# uat fat环境
/data/project/go-agent/go-agent-v0.6.1

# 生产环境和预发环境 
/data/jenkins/_jenkins/go-agent-v0.6.1

报错原因分析

panic堆栈

./bin/eac --env=local
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x2 addr=0x0 pc=0x1033b7d9c]

goroutine 1 [running]:
github.com/sirupsen/logrus.skywalking_operatorStaticMethodLogrusGetLogContext(0x1)
    skywalking_logrus_context.go:82 +0x3c
github.com/sirupsen/logrus.(*skywalking_operatorTypeLogrusWrapFormat).Format(0x1400003b1a0, 0x140007b6cb0)
    skywalking_logrus_logrus_format.go:45 +0x74
github.com/sirupsen/logrus.(*Entry).write(0x140007b6cb0)
    /opt/go/gopath/pkg/mod/cache/github.com/sirupsen/logrus@v1.9.3/entry.go:289 +0xf0
github.com/sirupsen/logrus.(*Entry).log(0x14000218070, 0x4, {0x140001a725c, 0x4})
    /opt/go/gopath/pkg/mod/cache/github.com/sirupsen/logrus@v1.9.3/entry.go:252 +0x318
github.com/sirupsen/logrus.(*Entry).Log(0x14000218070, 0x4, {0x1400063fbb0, 0x1, 0x1})
    /opt/go/gopath/pkg/mod/cache/github.com/sirupsen/logrus@v1.9.3/entry.go:304 +0x88
github.com/sirupsen/logrus.(*Entry).Logf(0x14000218070, 0x4, {0x10481760e, 0x6}, {0x1400063fdf8, 0x1, 0x1})
    /opt/go/gopath/pkg/mod/cache/github.com/sirupsen/logrus@v1.9.3/entry.go:349 +0x104
github.com/sirupsen/logrus.(*Logger).Logf(0x14000220000, 0x4, {0x10481760e, 0x6}, {0x1400063fdf8, 0x1, 0x1})
    /opt/go/gopath/pkg/mod/cache/github.com/sirupsen/logrus@v1.9.3/logger.go:154 +0x74
github.com/sirupsen/logrus.(*Logger).Infof(0x14000220000, {0x10481760e, 0x6}, {0x1400063fdf8, 0x1, 0x1})
    /opt/go/gopath/pkg/mod/cache/github.com/sirupsen/logrus@v1.9.3/logger.go:168 +0x54
github.com/sirupsen/logrus.Infof({0x10481760e, 0x6}, {0x1400063fdf8, 0x1, 0x1})
    /opt/go/gopath/pkg/mod/cache/github.com/sirupsen/logrus@v1.9.3/exported.go:199 +0x50
ezone.ksyun.com/ezone/ksyun-online/enterprise-account/pkg/env.init.0()
    /Users/sairo/Project/code/enterprise-account/pkg/env/env.go:63 +0xe4

从panic堆栈看是项目中环境配置的init方法使用logrus记录日志,但是logrus内部的Format被skywalking agent重写了,在调用时由于Report没有完成初始化从而触发了panic。

Panic 链路分析:
  logrus.Infof() → Logger.Infof() → Entry.Logf() → Entry.write() → WrapFormat.Format() → skywalking_operatorStaticMethodLogrusGetLogContext() → nil panic

这个错误怎么发生的?

build阶段会按这个模版生成具体的代码用来执行初始化操作

go agent本质上也是在init方法中执行初始化操作,但是这个执行顺序不确定,项目中的init方法调用logrus打印日志时GetLogContext尚未完成初始化,而新版的agent源码又没有对nil做判断就导致panic了。

知识点:Go init方法执行顺序

Go语言程序的初始化和执行总是从main.main函数开始的。

但是如果main包导入了其它的包,则会按照顺序将它们包含进main包里(这里的导入顺序依赖具体实现,一般可能是以文件名或包路径名的字符串顺序导入)。如果某个包被多次导入的话,在执行的时候只会导入一次。当一个包被导入时,如果它还导入了其它的包,则先将其它的包包含进来,然后创建和初始化这个包的常量和变量,再调用包里的init函数,如果一个包有多个init函数的话,调用顺序未定义(实现可能是以文件名的顺序调用),同一个文件内的多个init则是以出现的顺序依次调用(init不是普通函数,可以定义有多个,所以也不能被其它函数调用)。最后,当main包的所有包级常量、变量被创建和初始化完成,并且init函数被执行后,才会进入main.main函数,程序开始正常执行。

关键问题:为什么go1.21+agent v0.4就正常?

因为agent v0.4更健壮,对nil做了检查,最新版agent v0.6反而没有nil检查!

agent v0.4

题外话

编译skywalking go agent源码时不要使用Make release指令,脚本编译的不是本地代码,而是会先git clone 官方代码再编译,所以,直接使用go build就好~

评论