Skip to content

Conversation

@flimzy
Copy link
Contributor

@flimzy flimzy commented Nov 9, 2023

This addresses half of #1401. The easy half, honestly.

I have an implementation of the other half we've been using in our own project, but it will need some polish before making it public, so I'm submitting this PR now to gauge interest of the project maintainers. If we like this, I'll work on polishing the other half in a later PR.

@flimzy
Copy link
Contributor Author

flimzy commented Nov 9, 2023

I see that CI is failing, and probably needs to be updated.

For the tests in this PR to be meaningful, we'll alsoneed to run tests against Go 1.21 (since the new code is behind a build tag).

If you'd like me to create a PR to address any of these, please let me know. I'm quite happy to contribute, but don't want to waste my effort if the package maintainers aren't interested 😉

@thaJeztah
Copy link
Collaborator

(did a quick rebase to make CI run)

Copy link

@mdelapenya mdelapenya left a comment

Choose a reason for hiding this comment

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

@thaJeztah this LGTM, although I added some minor concerns regarding the usage of a logger Vs a handler. It's basically about not swallowing errors.

@flimzy 👋 Happy to support you here and make progress to eventually merge this into the project.

Comment on lines +73 to +78
h.logger.Log(
entry.Context,
h.toSlogLevel(entry.Level).Level(),
entry.Message,
attrs...,
)
Copy link

@mdelapenya mdelapenya Dec 11, 2025

Choose a reason for hiding this comment

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

suggestion: Logger.Log() does not expose handler errors to the caller, and there are some situations where the handler can error (file permissions, disk full...), so I think this is silently discarding errors, even if the logging failed.

If you use a slog.Handler in the SlogHook instead of a logger, and build a record here, you can handle those errors here. Please noticed I added an optional errorHandler, that could be configured in the constructor or via functional options.... or simply completely skip it and always print 🤷

func (h *SlogHook) Fire(entry *logrus.Entry) error {
	attrs := make([]interface{}, 0, len(entry.Data))
	for k, v := range entry.Data {
		attrs = append(attrs, slog.Any(k, v))
	}

	record := slog.NewRecord(entry.Time, h.toSlogLevel(entry.Level).Level(),
		entry.Message, 0)
	for _, attr := range attrs {
		record.AddAttrs(attr.(slog.Attr))
	}

	err := h.handler.Handle(entry.Context, record)
	if err != nil {
		if h.errorHandler != nil {
			h.errorHandler(err, entry)
		} else {
			// print to stderr if no custom error handler is set
			fmt.Fprintf(os.Stderr, "slog handler error: %v\n", err)
		}
		return err
	}
	return nil
}

If this is of your interest, we should also extend the tests, and include a README.

Thoughts?

// SlogHook sends logs to slog.
type SlogHook struct {
logger *slog.Logger
LevelMapper LevelMapper

Choose a reason for hiding this comment

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

question: is it possible to change the levelMapper? Else, I'd keep it private

Suggested change
LevelMapper LevelMapper
levelMapper LevelMapper

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.

3 participants