我正在使用一个新的 go 服务,我有一个 SetupLogger
实用函数,它创建一个新的 go-kit 日志结构实例 log.Logger
。
从在单独的 go-routines 中处理请求的代码调用此方法是否安全?
package utils
import (
"fmt"
"github.com/go-kit/kit/log"
"io"
"os"
"path/filepath"
)
// If the environment-specified directory for writing log files exists, open the existing log file
// if it already exists or create a log file if no log file exists.
// If the environment-specified directory for writing log files does not exist, configure the logger
// to log to process stdout.
// Returns an instance of go-kit logger
func SetupLogger() log.Logger {
var logWriter io.Writer
var err error
LOG_FILE_DIR := os.Getenv("CRAFT_API_LOG_FILE_DIR")
LOG_FILE_NAME := os.Getenv("CRAFT_API_LOG_FILE_NAME")
fullLogFilePath := filepath.Join(
LOG_FILE_DIR,
LOG_FILE_NAME,
)
if dirExists, _ := Exists(&ExistsOsCheckerStruct{}, LOG_FILE_DIR); dirExists {
if logFileExists, _ := Exists(&ExistsOsCheckerStruct{}, fullLogFilePath); !logFileExists {
os.Create(fullLogFilePath)
}
logWriter, err = os.OpenFile(fullLogFilePath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
if err != nil {
fmt.Println("Could not open log file. ", err)
}
} else {
logWriter = os.Stdout
}
return log.NewContext(log.NewJSONLogger(logWriter)).With(
"timestamp", log.DefaultTimestampUTC,
"caller", log.DefaultCaller,
)
}
最佳答案
第一个建议:使用-race
标志go build
和go test
。如果您有竞争条件,它几乎总能告诉您。尽管在这种情况下可能不会,因为您最终可能会同时调用 os.Create()
和 os.OpenFile()
。
因此,第二个建议是尽可能避免“如果它存在/匹配/具有权限然后打开/删除/随便”模式。
该模式会导致 TOCTTOU(检查时间到使用时间)错误,这通常是一个安全错误,至少会导致数据丢失。
为了避免它,要么将检查和使用包装到同一个互斥锁中,要么使用原子操作,例如创建文件的 OpenFile 调用,或者如果文件已经存在则返回错误(尽管是技术性的,它被锁定在操作系统中内核。就像原子 CPU 操作如何锁定在硬件总线中一样。)。
在你这里的情况下,我不太确定为什么你有两个 Open 调用,因为它看起来只需要一个就可以完成这项工作。
关于Go logging struct 实例化实用方法的 Goroutine 线程安全,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36458566/