Go日志结构实例化实用方法的Goroutine线程安全性

huangapple go评论152阅读模式
英文:

Goroutine thread safety of Go logging struct instantiation utility method

问题

我正在使用一个新的Go服务,并且我有一个SetupLogger实用函数,用于创建go-kit日志结构log.Logger的新实例。

这个方法在处理独立的Go协程中的请求的代码中调用是安全的吗?

package utils

import (
	"fmt"
	"github.com/go-kit/kit/log"
	"io"
	"os"
	"path/filepath"
)

// 如果环境指定的用于写入日志文件的目录存在,则打开现有的日志文件(如果存在),
// 如果不存在日志文件,则创建一个日志文件。
// 如果环境指定的用于写入日志文件的目录不存在,则配置日志记录器以记录到进程的标准输出。
// 返回go-kit日志记录器的实例
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,
	)
}
英文:

I'm working with a new go service and I have a SetupLogger utility function that creates a new instance of go-kit's logging struct log.Logger.

Is this method safe to invoke from code that's handling requests inside separate 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,
	)
}

答案1

得分: 1

由于您设置Logger只涉及库实例化、如果不存在则创建日志文件、打开日志文件,并且没有涉及写入操作,所以从不同的go-routines调用它不会有问题,因为共享数据不会被篡改。

附注:
从设计角度来看,如果Logger写入同一个文件,将Logger的唯一实例传递给日志记录,这样做是有意义的,这将防止两个go-routines同时调用设置函数。

英文:

Since your setting up of your Logger only involves library instantiation, creating a log file if it doesn't exist, opening the log file and no writing involved there will be no problem calling it from different go-routines since the shared data is not getting tampered with.

Side note:
Design wise it makes sense (assuming Logger is writing to the same file) to pass around the only instantiated instance of Logger for logging which would prevent two go-routines calling your setup function at the same time.

答案2

得分: 1

第一条建议:在使用go buildgo test时,使用-race标志。它几乎总能告诉你是否存在竞态条件。尽管在这种情况下可能不会,因为你可能同时调用os.Create()os.OpenFile()

所以,第二条建议是尽量避免使用“如果存在/匹配/具有权限,则打开/删除/执行其他操作”的模式。

这种模式会导致TOCTTOU(检查时间到使用时间)错误,这通常是一个安全漏洞,至少可能导致数据丢失。

为了避免这种情况,要么将检查和使用包装在同一个互斥锁中,要么使用原子操作,比如一个OpenFile调用,如果文件已经存在则返回错误(尽管从技术上讲,它被锁定在操作系统内核中,就像原子CPU操作被锁定在硬件总线上一样)。

在你的这个例子中,我不太确定为什么你有两个Open调用,因为看起来只需要一个就可以完成工作。

英文:

First recommendation: Use the -race flag to go build and go test. It will almost always be able to tell you if you have a race condition. Although it might not in this case since you could end up calling your os.Create() and your os.OpenFile() simultaneously.

So, second recommendation is to avoid, if at all possible, the "If it exists/matches/has permissions Then open/delete/whatever" pattern.

That pattern leads to the TOCTTOU (Time of Check To Time Of Use) bug, which is often a security bug and can at the very least lead to data loss.

To avoid it either wrap the check and use into the same mutex, or use atomic operations, such as an OpenFile call that creates the file or returns an error if it already existed (although to be technical, its locked inside the OS kernel. Just like how atomic CPU ops are locked in the hardware bus.).

In your case here I am not quite sure why you have two Open calls since it looks like just one would do the job.

huangapple
  • 本文由 发表于 2016年4月7日 01:45:58
  • 转载请务必保留本文链接:https://go.coder-hub.com/36458566.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定