rs / zerolog

Zero Allocation JSON Logger
MIT License
10.62k stars 572 forks source link

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

Open capthiron opened 2 years ago

capthiron commented 2 years ago

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

marcelhuth commented 1 year ago

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