fleet/server/platform/logging/multi_handler.go
Victor Lyuboslavsky a10f05486f
Added OTEL log export support (#39279)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #38607

Contributor docs update:
https://github.com/fleetdm/fleet/pull/39285/changes
Another contributor docs update:
https://github.com/fleetdm/fleet/pull/39402/changes

Also:
- renamed OtelHandler to OtelTracingHandler
- made "opentelemetry" be the default when tracing is enabled
- updated OTEL dependencies

# Checklist for submitter

- [x] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.

## Testing

- [x] Added/updated automated tests
- [x] QA'd all new/changed functionality manually

## New Fleet configuration settings

- [x] Setting(s) is/are explicitly excluded from GitOps

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Added OpenTelemetry log export capability, enabling logs to be sent to
OpenTelemetry collectors.
* New configuration option `logging.otel_logs_enabled` (requires tracing
to be enabled).

* **Chores**
* Updated OpenTelemetry dependencies to v1.40.0 with latest OTLP
exporters and logging support.
* Updated dependencies including gRPC (v1.78.0), Google libraries, and
cryptography packages.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-02-06 18:57:28 -06:00

61 lines
1.7 KiB
Go

package logging
import (
"context"
"errors"
"log/slog"
)
// MultiHandler sends log records to multiple handlers.
type MultiHandler struct {
handlers []slog.Handler
}
func NewMultiHandler(handlers ...slog.Handler) *MultiHandler {
return &MultiHandler{handlers: handlers}
}
// Enabled reports whether any handler handles records at the given level.
func (h *MultiHandler) Enabled(ctx context.Context, level slog.Level) bool {
for _, handler := range h.handlers {
if handler.Enabled(ctx, level) {
return true
}
}
return false
}
// Handle sends the record to all enabled handlers.
// It continues processing all handlers even if one fails, and returns
// a combined error of all failures using errors.Join.
func (h *MultiHandler) Handle(ctx context.Context, r slog.Record) error {
var errs []error
for _, handler := range h.handlers {
if handler.Enabled(ctx, r.Level) {
if err := handler.Handle(ctx, r.Clone()); err != nil {
errs = append(errs, err)
}
}
}
return errors.Join(errs...)
}
// WithAttrs returns a new MultiHandler with the given attributes added to all handlers.
func (h *MultiHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
handlers := make([]slog.Handler, len(h.handlers))
for i, handler := range h.handlers {
handlers[i] = handler.WithAttrs(attrs)
}
return &MultiHandler{handlers: handlers}
}
// WithGroup returns a new MultiHandler with the given group name applied to all handlers.
func (h *MultiHandler) WithGroup(name string) slog.Handler {
handlers := make([]slog.Handler, len(h.handlers))
for i, handler := range h.handlers {
handlers[i] = handler.WithGroup(name)
}
return &MultiHandler{handlers: handlers}
}
var _ slog.Handler = (*MultiHandler)(nil)