Skip to content

feat: structured logging support for zerolog#1031

Closed
aldy505 wants to merge 1 commit intogetsentry:masterfrom
aldy505:feat/zerolog-logging-integration
Closed

feat: structured logging support for zerolog#1031
aldy505 wants to merge 1 commit intogetsentry:masterfrom
aldy505:feat/zerolog-logging-integration

Conversation

@aldy505
Copy link
Copy Markdown
Contributor

@aldy505 aldy505 commented Jun 8, 2025

Closes #1029

@codecov
Copy link
Copy Markdown

codecov bot commented Jun 8, 2025

Codecov Report

Attention: Patch coverage is 93.33333% with 5 lines in your changes missing coverage. Please review.

Project coverage is 85.65%. Comparing base (abc8abb) to head (5f87761).
Report is 3 commits behind head on master.

Files with missing lines Patch % Lines
zerolog/sentrylogger.go 93.33% 4 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1031      +/-   ##
==========================================
+ Coverage   85.45%   85.65%   +0.19%     
==========================================
  Files          55       56       +1     
  Lines        5500     5575      +75     
==========================================
+ Hits         4700     4775      +75     
  Misses        655      655              
  Partials      145      145              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment on lines +367 to +373
name: "String array attribute",
logFunc: func(c *zerolog.Event) *zerolog.Event {
return c.Strs("foo", []string{"bar", "baz"})
},
wantAttributes: map[string]sentry.Attribute{
"foo.0": {Value: "bar", Type: "string"},
"foo.1": {Value: "baz", Type: "string"},
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if this is the right behavior. If we see into open-telemetry/opentelemetry-specification#3982, they said if we have an array attribute, it shouldn't be separated like this. But I don't really know the internals of the Trace Explorer.

The options would be:

  1. foo.0 => bar, foo.1 => baz
  2. foo => ["bar", "baz"]

Perhaps @AbhiPrasad can help with this?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's please not unfurl array properties into their own keys. Let's send them as a string for now, with the intention that in the future sentry ingestion will support arrays.

We do this unfurling pattern for message parameters (sentry.message.parameter.0) because we needed to make them indexed + queryable immediately for better product UX in the short term, but I want to avoid arbitrary user arrays to be done this way.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good to know. Thanks Abhi!

Copy link
Copy Markdown
Contributor

@AbhiPrasad AbhiPrasad left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of using a writer, can we use the hook system in zerolog? https://github.com/rs/zerolog/blob/master/README.md#hooks

I'd like us to avoid the overhead of encoding/decoding json if we can.

something like

type SentryLogHook struct{}

func (h SentryLogHook) Run(e *zerolog.Event, level zerolog.Level, msg string) {
    ctx := e.GetCtx()
    l := sentry.NewLogger(ctx)
    // need more complicated logic to parse the zerolog event
    l.WithLevel(convertLevel(level)).Log(ctx, msg)
}

logger := zerolog.New(os.Stdout).Hook(SentryLogHook{})

If we do have to use a writer, we should make it async.

case zerolog.TimestampFieldName:
continue
default:
switch valueType := value.(type) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't we have a case for int here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did test it with every possible Go types. Yet everything falls down to just 3 types (plus their array variant): string, boolean, float64

Copy link
Copy Markdown
Contributor

@AbhiPrasad AbhiPrasad Jun 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll want to still explicitly use attribute.Int because we treat these differently in the backend. In the future there will be operations you can only do on integers you can can't use on floats.

// Close should not be called directly.
// It should be called internally by zerolog.
func (s SentryLogger) Close() error {
sentry.Flush(time.Second)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we make this configurable?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps, yes.

l.SetAttributes(attribute.Bool(field, valueType))
case float64:
l.SetAttributes(attribute.Float64(field, float64(valueType)))
case []any:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should have a default case here that just serializes the value and sends as a string type.

hub = sentry.NewHub(nil, sentry.NewScope())
}

ctx = sentry.SetHubOnContext(context.Background(), hub)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ctx = sentry.SetHubOnContext(context.Background(), hub)
ctx = sentry.SetHubOnContext(ctx, hub)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No can't do. You must assume that the previous ctx is nil.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

really? do you have links to code/docs about this?

var message string
for field, value := range evt {
switch field {
case zerolog.LevelFieldName:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these names can be customized: https://github.com/rs/zerolog/blob/master/README.md#customize-automatic-field-names

we prob need a helper

func (s SentryLogger) getFieldNames() (level, message, timestamp string) {
	return zerolog.LevelFieldName,
		zerolog.MessageFieldName,
		zerolog.TimestampFieldName
}

@aldy505
Copy link
Copy Markdown
Contributor Author

aldy505 commented Jun 9, 2025

Instead of using a writer, can we use the hook system in zerolog? https://github.com/rs/zerolog/blob/master/README.md#hooks

I'd like us to avoid the overhead of encoding/decoding json if we can.

something like

type SentryLogHook struct{}

func (h SentryLogHook) Run(e *zerolog.Event, level zerolog.Level, msg string) {
    ctx := e.GetCtx()
    l := sentry.NewLogger(ctx)
    // need more complicated logic to parse the zerolog event
    l.WithLevel(convertLevel(level)).Log(ctx, msg)
}

logger := zerolog.New(os.Stdout).Hook(SentryLogHook{})

@AbhiPrasad I've tried the Hook, didn't work. The Hook has this interface signature:

Run(e *Event, level Level, message string)

In which:

  • e *Event is write only, you cannot read the properties/attributes already set to that event
  • level Level is useful yes, but for level only
  • message string only contains the message, you can't acquire the attributes here

If we do have to use a writer, we should make it async.

Yes we might want to go to this direction.

@cleptric
Copy link
Copy Markdown
Member

cleptric commented Jun 9, 2025

Can we please discuss implementation prior opening PRs @aldy505. This is really unproductive else.

@AbhiPrasad
Copy link
Copy Markdown
Contributor

write only, you cannot read the properties/attributes already set to that event

wow thats annoying

Let's explore the async writer then. Let's discuss this in the GH issue, and then come back to the PR. We might even want to open up a new PR so we don't get lost in the feedback here.

@giortzisg
Copy link
Copy Markdown
Contributor

Hey @aldy505, I was already working on the integration, and since there are enough changes from the current version, it will be easier for me to close the PR and take over myself.

@giortzisg giortzisg closed this Jun 16, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Structured logging support for zerolog

4 participants