Skip to content

Commit

Permalink
feat(trace): enhance span creation and fix trace continuity issues
Browse files Browse the repository at this point in the history
This update improves trace functionality with several key enhancements:

- Implement context-based trace span creation to better manage trace lifecycle
- Restructure key hierarchy for improved organization and efficiency
- Fix trace discontinuity when chaining .WithGroup().With().Info() calls
- Refactor complex logic for better maintainability

The changes provide more reliable tracing capabilities while making the codebase
more maintainable and efficient.

Signed-off-by: yakumioto <[email protected]>
  • Loading branch information
yakumioto committed Dec 4, 2024
1 parent 30783f7 commit 5b3c142
Show file tree
Hide file tree
Showing 9 changed files with 746 additions and 301 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,6 @@ jobs:
- name: Run coverage
run: go test -coverprofile=coverage.txt
- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v4.0.1
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
192 changes: 137 additions & 55 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,40 @@
# otelslog

[![CI Status](https://github.com/yakumioto/otelslog/actions/workflows/main.yaml/badge.svg)](https://github.com/yakumioto/otelslog/actions/workflows/go.yml)
[![Go Report Card](https://goreportcard.com/badge/github.com/yakumioto/otelslog)](https://goreportcard.com/report/github.com/yakumioto/otelslog)
[![codecov](https://codecov.io/github/yakumioto/otelslog/graph/badge.svg?token=6ODsohX0G6)](https://codecov.io/github/yakumioto/otelslog)
[![codebeat badge](https://codebeat.co/badges/dd9f3cd1-265a-4de0-be8a-0d6fcf690220)](https://codebeat.co/projects/github-com-yakumioto-otelslog-main)
[![GoDoc](https://pkg.go.dev/badge/github.com/yakumioto/otelslog)](https://pkg.go.dev/github.com/yakumioto/otelslog)
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
[![Release](https://img.shields.io/github/v/release/yakumioto/otelslog.svg)](https://github.com/yakumioto/otelslog/releases)

otelslog is a Go package that seamlessly integrates structured logging (slog) with OpenTelemetry tracing. By bridging these two essential observability tools, otelslog makes it easier to correlate logs with distributed traces, providing deeper insights into your application's behavior and performance.
otelslog is a Go package that seamlessly integrates structured logging (slog) with OpenTelemetry tracing. It enriches your observability stack by automatically correlating logs with distributed traces, providing deep insights into your application's behavior. The package maintains the simplicity of slog while adding powerful tracing capabilities that help you understand the flow of operations across your distributed system.

[中文版](README_zhCN.md)

## Features
## Key Features

otelslog enhances your application's observability by providing:
otelslog enhances your application's observability through several powerful features:

- Automatic correlation between logs and traces through trace and span ID injection
- Flexible span creation based on log levels
- Support for mandatory spans that are always created regardless of log level
- Rich support for nested attribute groups in both logs and traces
- Customizable configuration through functional options
- Thread-safe operation for concurrent use
* Automatic Trace Context Integration
* Seamless injection of trace and span IDs into log records
* Built-in context propagation across service boundaries
* Support for nested spans with proper parent-child relationships

* Flexible Configuration
* Customizable field names for trace and span IDs
* Configurable minimum log level for trace creation
* Optional span event recording
* Support for mandatory spans that bypass log level filtering

* Rich Structured Data Support
* Complete support for nested attribute groups in both logs and traces
* Type-aware attribute conversion between slog and OpenTelemetry formats
* Preservation of attribute hierarchies in distributed traces

* Operational Excellence
* Thread-safe design for concurrent use
* Memory-efficient attribute handling

## Installation

Expand All @@ -31,115 +46,182 @@ go get github.com/yakumioto/otelslog

## Quick Start

Here's how to get started with otelslog:
Here's a minimal example to get you started with otelslog:

```go
package main

import (
"context"
"log/slog"
"os"

"github.com/yakumioto/otelslog"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/sdk/trace"
)

func main() {
// Initialize your tracer
// Initialize a basic tracer for demonstration
tp := trace.NewTracerProvider()
otel.SetTracerProvider(tp)
defer tp.Shutdown(context.Background())

// Set up the default logger with otelslog handler
slog.SetDefault(slog.New(
otelslog.NewHandler(slog.NewJSONHandler(os.Stdout, nil)),
))

// Create a span and log with it
span := otelslog.NewSpan("process-request")
// Create a span and include it in logging
span := otelslog.NewSpanContext("service", "process-request")
slog.Info("handling request",
"operation", span,
"user_id", "123",
slog.Group("user",
slog.String("id", "123"),
slog.String("role", "admin"),
),
)
defer span.End()
}
```

## Advanced Usage

### Custom Configuration
### Configuring the Handler

You can customize the handler's behavior using functional options:
Customize the handler's behavior using functional options:

```go
handler := otelslog.NewHandler(
slog.NewJSONHandler(os.Stdout, nil),
otelslog.WithTraceIDKey("trace_id"),
otelslog.WithSpanIDKey("span_id"),
otelslog.WithTraceLevel(slog.LevelDebug),
otelslog.WithNoSpanEvents(),
slog.SetDefault(slog.New(
otelslog.NewHandler(
slog.NewJSONHandler(os.Stdout, nil),
otelslog.WithTraceIDKey("trace_id"), // Custom trace ID field
otelslog.WithSpanIDKey("span_id"), // Custom span ID field
otelslog.WithTraceLevel(slog.LevelDebug), // Set minimum trace level
otelslog.WithNoSpanEvents(), // Disable span events
),
))
```

### Context Propagation and Nested Spans

Track operations across your application with proper context propagation:

```go
// Create a root span
span1 := otelslog.NewSpanContext("service", "parent-operation")
slog.Info("starting parent operation",
"operation", span1,
"request_id", "req-123",
)

// Create a child span with context
span2Ctx := otelslog.NewSpanContextWithContext(span1, "service", "child-operation")
slog.InfoContext(span2Ctx, "processing sub-operation",
slog.Group("metrics",
slog.Int("items_processed", 42),
slog.Duration("processing_time", time.Second),
),
)

defer span2Ctx.Done()
defer span1.End()
```

### Working with Groups
### Mandatory Spans and Critical Operations

otelslog fully supports slog's grouping functionality:
Ensure critical operations are always traced regardless of log level:

```go
span := otelslog.NewSpan("process-request")
slog.WithGroup("request").Info("handling request",
span := otelslog.NewMustSpanContext("service", "critical-operation")
slog.Info("processing critical request",
"operation", span,
slog.Group("user",
slog.String("id", userID),
slog.String("role", userRole),
slog.Group("transaction",
slog.String("id", "tx-789"),
slog.Float64("amount", 1299.99),
),
)
defer span.End()
```

### Mandatory Spans
### Working with Structured Data

For critical operations that should always be traced:
Organize your logging data using slog's powerful grouping features:

```go
span := otelslog.NewMustSpan("critical-operation")
slog.Info("processing important request",
span := otelslog.NewSpanContext("service", "user-management")
slog.Default().WithGroup("request").Info("updating user profile",
"operation", span,
"user_id", userID,
slog.Group("user",
slog.String("id", "user-456"),
slog.Group("changes",
slog.String("email", "[email protected]"),
slog.String("role", "admin"),
),
),
)
defer span.End()
```

### Nested Spans
## Integration with OpenTelemetry

otelslog supports creating nested spans for tracking sub-operations:
Set up a complete tracing pipeline with OTLP export:

```go
span1 := otelslog.NewSpan("parent-operation")
slog.Info("starting parent operation", "operation", span1)

span2 := otelslog.NewSpan("child-operation")
slog.InfoContext(span1.Context(), "performing sub-operation", "operation", span2)
func initTracer(ctx context.Context) (func(), error) {
exporter, err := otlptrace.New(
ctx,
otlptracegrpc.NewClient(
otlptracegrpc.WithEndpoint("localhost:4317"),
otlptracegrpc.WithInsecure(),
),
)
if err != nil {
return nil, err
}

tp := sdktrace.NewTracerProvider(
sdktrace.WithBatcher(exporter),
sdktrace.WithResource(resource.NewWithAttributes(
semconv.ServiceName("your-service"),
semconv.ServiceVersion("1.0.0"),
)),
sdktrace.WithSampler(sdktrace.AlwaysSample()),
)
otel.SetTracerProvider(tp)

defer span2.End()
defer span1.End()
return func() { tp.Shutdown(ctx) }, nil
}
```

## Best Practices

To get the most out of otelslog:
To maximize the benefits of otelslog in your application:

1. Always use meaningful span names that clearly describe the operation
2. Use `defer span.End()` immediately after span creation to ensure proper cleanup
3. Configure appropriate trace levels to balance visibility with performance
4. Use groups to organize related attributes in both logs and traces
5. Consider using `NewMustSpan` for critical operations that should always be traced
6. Leverage span context propagation for tracking request flows across service boundaries
* Design spans to reflect your application's logical operations. Choose span names that clearly describe what operation is being performed, making traces easier to understand and analyze.

* Create a consistent attribute hierarchy using groups. Organize related attributes together to maintain clarity in both logs and traces, making it easier to correlate information across your observability tools.

* Use context propagation effectively. Always pass context through your application's call chain to maintain proper parent-child relationships between spans and ensure accurate distributed tracing.

* Consider performance implications. Configure appropriate trace levels and use WithNoSpanEvents when span events aren't needed to optimize performance in high-throughput scenarios.

* Handle span lifecycle properly. Always use defer for span.End() calls immediately after span creation to ensure proper cleanup and accurate duration measurements.

* Leverage mandatory spans judiciously. Use NewMustSpanContext for operations that must be traced regardless of log level, but be mindful of the additional overhead.

## Acknowledgements

This project was inspired by [slog-otel](https://github.com/remychantenay/slog-otel). We would like to thank its creators and contributors for their innovative work in combining structured logging with OpenTelemetry.
This project was inspired by [slog-otel](https://github.com/remychantenay/slog-otel). We extend our gratitude to its creators and contributors for their pioneering work in combining structured logging with OpenTelemetry.

## Related Projects

If you're interested in structured logging and OpenTelemetry integration, you might also find these projects useful:
Explore these related projects to enhance your observability stack:

* [slog-otel](https://github.com/remychantenay/slog-otel) - Another approach to bringing OpenTelemetry to slog
* [slog](https://pkg.go.dev/log/slog) - Go's official structured logging package
* [OpenTelemetry Go](https://github.com/open-telemetry/opentelemetry-go) - The official OpenTelemetry SDK for Go

## License

- [slog-otel](https://github.com/remychantenay/slog-otel) - A handler bringing OpenTelemetry to slog
- [slog](https://pkg.go.dev/log/[email protected]) - The official structured logging package for Go
- [OpenTelemetry Go](https://github.com/open-telemetry/opentelemetry-go) - The official OpenTelemetry SDK for Go
This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for details.
Loading

0 comments on commit 5b3c142

Please sign in to comment.