zerolog icon indicating copy to clipboard operation
zerolog copied to clipboard

QUESTION: How to encode caller information to match GCP LogEntrySourceLocation?

Open capthiron opened this issue 3 years ago • 1 comments

Hey there! 🙂

I am trying to encode the caller information (file, line & function) so it matches the requirements for GCP LogEntry's LogEntrySourceLocation field. This is important so the caller information can be automatically extracted by GCP's LogAgent.

A result should look something like the following:

{
  "caller":{
    "file":"get_data.go",
    "line":"142",
    "function":""
  }
}

Unfortunately I could not see a way to implement a custom zerolog.CallerMarshalFunc() to match the required format with a nested object.

// excerpt from event.go:743
func (e *Event) caller(skip int) *Event {
	if e == nil {
		return e
	}
	pc, file, line, ok := runtime.Caller(skip + e.skipFrame)
	if !ok {
		return e
	}
	e.buf = enc.AppendString(enc.AppendKey(e.buf, CallerFieldName), CallerMarshalFunc(pc, file, line))
	return e
}

I am wondering whether it is possible to achieve this functionality with how the caller func is currently implemented with enc.AppendString(). I hope I was able to explain the problem in an understandable way.

Great regards, Dario

capthiron avatar Sep 07 '22 15:09 capthiron

Hi @capthiron,

currently I'm looking into this as well. I really do want to have a possibility to get the caller in GCP LogEntrySourceLocation format. My current workaround works, but is really not a good practice I guess. I completely ignore the build in possibilities of zerolog and do not use With().Caller() at all. I build a hook for getting the caller information in every log entry:

type CallerHook struct{}

type logEntrySourceLocation struct {
	File     string `json:"file"`
	Line     int    `json:"line,string"`
	Function string `json:"function,omitempty"`
}

func (h *CallerHook) Run(e *zerolog.Event, level zerolog.Level, msg string) {
	if pc, file, line, ok := runtime.Caller(3); ok {
		sourceLocation := logEntrySourceLocation{
			File:     file,
			Line:     line,
			Function: runtime.FuncForPC(pc).Name(),
		}
		e.Interface("sourceLocation", sourceLocation)
	}
}

This hook is added to my global zerolog logger. That works very nicely, but actually ignores the options zerolog gives.

Another idea I had was using the CallerMarshalFunc in zerolog:

func marshalCaller(pc uintptr, file string, line int) string {
	sourceLocation := logEntrySourceLocation{
		File:     file,
		Line:     line,
		Function: runtime.FuncForPC(pc).Name(),
	}

	bs, err := json.Marshal(sourceLocation)
	if err != nil {
		panic(err)
	}

	return string(bs)
}

But this gives you a quoted string when zerolog internally calls enc.AppendString in func (e *Event) caller(skip int) *Event as Encoder.AppendString quotes the JSON string from my marshalCaller func (at least from my understanding)

Probably anybody who is reading this issue has some others ideas, but for now I guess zerolog's caller function has to be rewritten to handle caller information in JSON format.

Regards, Marcel

marcelhuth avatar May 25 '23 18:05 marcelhuth