rolog

package module
v0.0.0-...-b99c4da Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 4, 2026 License: Apache-2.0 Imports: 4 Imported by: 0

README

Slog Plugin

The Slog plugin provides operators for structured logging observables using Go's built-in log/slog package.

Installation

go get github.com/samber/ro/plugins/observability/slog

Operators

Log

Logs all observable events (next, error, complete) using slog at the specified level.

import (
    "log/slog"
    "os"
    "github.com/samber/ro"
    roslog "github.com/samber/ro/plugins/observability/slog"
)

// Create a slog logger
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))

observable := ro.Pipe1(
    ro.Just(1, 2, 3, 4, 5),
    roslog.Log[int](*logger, slog.LevelInfo),
)

subscription := observable.Subscribe(ro.NoopObserver[int]())
defer subscription.Unsubscribe()

// Output:
// time=2024-01-01T12:00:00.000Z level=INFO msg="ro.Next: 1"
// time=2024-01-01T12:00:00.000Z level=INFO msg="ro.Next: 2"
// time=2024-01-01T12:00:00.000Z level=INFO msg="ro.Next: 3"
// time=2024-01-01T12:00:00.000Z level=INFO msg="ro.Next: 4"
// time=2024-01-01T12:00:00.000Z level=INFO msg="ro.Next: 5"
// time=2024-01-01T12:00:00.000Z level=INFO msg="ro.Complete"
LogWithNotification

Logs observable events with structured attributes, including the value as a log attribute.

observable := ro.Pipe1(
    ro.Just("Hello", "World", "Golang"),
    roslog.LogWithNotification[string](*logger, slog.LevelInfo),
)

subscription := observable.Subscribe(ro.NoopObserver[string]())
defer subscription.Unsubscribe()

// Output:
// time=2024-01-01T12:00:00.000Z level=INFO msg="ro.Next" value=Hello
// time=2024-01-01T12:00:00.000Z level=INFO msg="ro.Next" value=World
// time=2024-01-01T12:00:00.000Z level=INFO msg="ro.Next" value=Golang
// time=2024-01-01T12:00:00.000Z level=INFO msg="ro.Complete"

Slog Levels

The plugin supports all slog log levels:

  • slog.LevelDebug
  • slog.LevelInfo
  • slog.LevelWarn
  • slog.LevelError
// Log at debug level
observable := ro.Pipe1(
    ro.Just(1, 2, 3),
    roslog.Log[int](*logger, slog.LevelDebug),
)

Context Support

All operators support context for structured logging:

ctx := context.WithValue(context.Background(), "request_id", "12345")

observable := ro.Pipe1(
    ro.Just(1, 2, 3),
    roslog.Log[int](*logger, slog.LevelInfo),
)

subscription := observable.SubscribeWithContext(ctx, ro.NoopObserver[int]())
defer subscription.Unsubscribe()

// Output:
// time=2024-01-01T12:00:00.000Z level=INFO msg="ro.Next: 1"
// time=2024-01-01T12:00:00.000Z level=INFO msg="ro.Next: 2"
// time=2024-01-01T12:00:00.000Z level=INFO msg="ro.Next: 3"
// time=2024-01-01T12:00:00.000Z level=INFO msg="ro.Complete"

Structured Data

The plugin works well with structured data types:

type User struct {
    Name string
    Age  int
}

observable := ro.Pipe1(
    ro.Just(
        User{Name: "Alice", Age: 30},
        User{Name: "Bob", Age: 25},
        User{Name: "Charlie", Age: 35},
    ),
    roslog.LogWithNotification[User](*logger, slog.LevelInfo),
)

subscription := observable.Subscribe(ro.NoopObserver[User]())
defer subscription.Unsubscribe()

// Output:
// time=2024-01-01T12:00:00.000Z level=INFO msg="ro.Next" value="{Alice 30}"
// time=2024-01-01T12:00:00.000Z level=INFO msg="ro.Next" value="{Bob 25}"
// time=2024-01-01T12:00:00.000Z level=INFO msg="ro.Next" value="{Charlie 35}"
// time=2024-01-01T12:00:00.000Z level=INFO msg="ro.Complete"

Logger Configuration

You can configure the slog logger with various handlers:

Text Handler
// Create a text handler
handler := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
    Level: slog.LevelInfo,
})
logger := slog.New(handler)

observable := ro.Pipe1(
    ro.Just(1, 2, 3),
    roslog.Log[int](*logger, slog.LevelInfo),
)
JSON Handler
// Create a JSON handler
handler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
    Level: slog.LevelInfo,
})
logger := slog.New(handler)

observable := ro.Pipe1(
    ro.Just(1, 2, 3),
    roslog.Log[int](*logger, slog.LevelInfo),
)
Custom Handler
// Create a custom handler
handler := slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
    Level: slog.LevelDebug,
    AddSource: true,
})
logger := slog.New(handler)

observable := ro.Pipe1(
    ro.Just(1, 2, 3),
    roslog.Log[int](*logger, slog.LevelInfo),
)

Error Handling

The plugin provides different error handling strategies:

Log Errors
// Log errors at a specific level
observable := ro.Pipe1(
    ro.Just(1, 2, 3),
    roslog.Log[int](*logger, slog.LevelError),
)
Structured Error Logging
// Log errors with structured attributes
observable := ro.Pipe1(
    ro.Just(1, 2, 3),
    roslog.LogWithNotification[int](*logger, slog.LevelError),
)

Real-world Example

Here's a practical example that logs API requests:

import (
    "context"
    "log/slog"
    "os"
    "github.com/samber/ro"
    roslog "github.com/samber/ro/plugins/observability/slog"
)

// Create a logger for API requests
handler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
    Level: slog.LevelInfo,
})
logger := slog.New(handler)

// Process API requests with logging
pipeline := ro.Pipe2(
    // Source: API requests
    ro.Just("GET /users", "POST /users", "GET /users/123"),
    // Log each request
    roslog.LogWithNotification[string](*logger, slog.LevelInfo),
)

subscription := pipeline.Subscribe(
    ro.NewObserver(
        func(request string) {
            // Process the request
        },
        func(err error) {
            // Handle error
        },
        func() {
            // Handle completion
        },
    ),
)
defer subscription.Unsubscribe()

Performance Considerations

  • The plugin uses Go's efficient slog logging mechanisms
  • Context propagation adds minimal overhead
  • Structured logging with attributes is optimized
  • Consider log level configuration for production
  • Use appropriate handlers for your environment
  • The plugin doesn't block the observable stream
  • Logging is done asynchronously to avoid performance impact
  • Slog provides efficient JSON and text formatting

Documentation

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Log

func Log[T any](logger slog.Logger, level slog.Level) func(ro.Observable[T]) ro.Observable[T]
Example
// Initialize slog logger
buff := bufio.NewWriter(os.Stdout)
logger := slog.New(slog.NewTextHandler(&timeFilterWriter{w: buff}, &slog.HandlerOptions{
	Level: slog.LevelInfo,
}))
defer buff.Flush()

// Log all notifications (Next, Error, Complete)
observable := ro.Pipe1(
	ro.Just(1, 2, 3, 4, 5),
	Log[int](*logger, slog.LevelInfo),
)

subscription := observable.Subscribe(ro.NoopObserver[int]())
defer subscription.Unsubscribe()
Output:
level=INFO msg="ro.Next: 1"
level=INFO msg="ro.Next: 2"
level=INFO msg="ro.Next: 3"
level=INFO msg="ro.Next: 4"
level=INFO msg="ro.Next: 5"
level=INFO msg=ro.Complete
Example (InPipeline)
// Initialize slog logger with mock handler that removes time
buff := bufio.NewWriter(os.Stdout)
logger := slog.New(slog.NewTextHandler(&timeFilterWriter{w: buff}, &slog.HandlerOptions{
	Level: slog.LevelInfo,
}))
defer buff.Flush()

// Use logging in a complex pipeline
observable := ro.Pipe3(
	ro.Just(1, 2, 3, 4, 5),
	ro.Filter(func(n int) bool { return n%2 == 0 }), // Keep even numbers
	Log[int](*logger, slog.LevelInfo),
	ro.Map(func(n int) string { return fmt.Sprintf("Even: %d", n) }),
)

subscription := observable.Subscribe(ro.NoopObserver[string]())
defer subscription.Unsubscribe()
Output:
level=INFO msg="ro.Next: 2"
level=INFO msg="ro.Next: 4"
level=INFO msg=ro.Complete
Example (WithContext)
// Initialize slog logger with mock handler that removes time
buff := bufio.NewWriter(os.Stdout)
logger := slog.New(slog.NewTextHandler(&timeFilterWriter{w: buff}, &slog.HandlerOptions{
	Level: slog.LevelInfo,
}))
defer buff.Flush()

// Log with context-aware operations
ctx := context.Background()

observable := ro.Pipe1(
	ro.Just("context", "aware", "logging"),
	LogWithNotification[string](*logger, slog.LevelInfo),
)

subscription := observable.SubscribeWithContext(ctx, ro.NoopObserver[string]())
defer subscription.Unsubscribe()
Output:
level=INFO msg=ro.Next value=context
level=INFO msg=ro.Next value=aware
level=INFO msg=ro.Next value=logging
level=INFO msg=ro.Complete
Example (WithCustomLevels)
// Initialize slog logger with mock handler that removes time
buff := bufio.NewWriter(os.Stdout)
logger := slog.New(slog.NewTextHandler(&timeFilterWriter{w: buff}, &slog.HandlerOptions{
	Level: slog.LevelInfo,
}))
defer buff.Flush()

// Demonstrate different log levels
observable := ro.Pipe1(
	ro.Just(1, 2, 3, 4, 5),
	Log[int](*logger, slog.LevelWarn),
)

subscription := observable.Subscribe(ro.NoopObserver[int]())
defer subscription.Unsubscribe()
Output:
level=WARN msg="ro.Next: 1"
level=WARN msg="ro.Next: 2"
level=WARN msg="ro.Next: 3"
level=WARN msg="ro.Next: 4"
level=WARN msg="ro.Next: 5"
level=WARN msg=ro.Complete
Example (WithError)
// Initialize slog logger with mock handler that removes time
buff := bufio.NewWriter(os.Stdout)
logger := slog.New(slog.NewTextHandler(&timeFilterWriter{w: buff}, &slog.HandlerOptions{
	Level: slog.LevelInfo,
}))
defer buff.Flush()

// Log including error notifications
observable := ro.Pipe1(
	ro.NewObservable(func(observer ro.Observer[int]) ro.Teardown {
		observer.Next(1)
		observer.Next(2)
		observer.Error(errors.New("something went wrong"))
		observer.Next(3) // This won't be emitted due to error
		return nil
	}),
	Log[int](*logger, slog.LevelError),
)

subscription := observable.Subscribe(ro.NoopObserver[int]())
defer subscription.Unsubscribe()
Output:
level=ERROR msg="ro.Next: 1"
level=ERROR msg="ro.Next: 2"
level=ERROR msg="ro.Error: something went wrong"

func LogWithNotification

func LogWithNotification[T any](logger slog.Logger, level slog.Level) func(ro.Observable[T]) ro.Observable[T]
Example
// Initialize slog logger with mock handler that removes time
buff := bufio.NewWriter(os.Stdout)
logger := slog.New(slog.NewTextHandler(&timeFilterWriter{w: buff}, &slog.HandlerOptions{
	Level: slog.LevelDebug,
}))
defer buff.Flush()

// Log with structured notification data
observable := ro.Pipe1(
	ro.Just("hello", "world", "golang"),
	LogWithNotification[string](*logger, slog.LevelDebug),
)

subscription := observable.Subscribe(ro.NoopObserver[string]())
defer subscription.Unsubscribe()
Output:
level=DEBUG msg=ro.Next value=hello
level=DEBUG msg=ro.Next value=world
level=DEBUG msg=ro.Next value=golang
level=DEBUG msg=ro.Complete

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL