Plack-Middleware-OpenTelemetry

 view release on metacpan or  search on metacpan

CLAUDE.md  view on Meta::CPAN

- OpenTelemetry (>= 0.018) - OpenTelemetry Perl SDK
- OpenTelemetry::SDK (>= 0.020) - OpenTelemetry SDK implementation
- Feature::Compat::Try - Modern try/catch syntax
- Syntax::Keyword::Dynamically - Dynamic scope management

### Middleware Functionality
The middleware automatically:
1. Creates OpenTelemetry spans for HTTP requests
2. Extracts tracing context from incoming requests
3. Sets standard HTTP semantic attributes following OpenTelemetry conventions
4. Handles both synchronous and streaming responses
5. Records exceptions and sets appropriate span status

### Configuration Options
- `include_client_errors`: Boolean flag to mark 4xx HTTP status codes as errors (default: false)
- `resource_attributes`: Hash reference for custom resource attributes

### Testing Environment
Tests use the console exporter (`OTEL_TRACES_EXPORTER=console`) for development and debugging.

## Development Workflow

lib/Plack/Middleware/OpenTelemetry.pm  view on Meta::CPAN

The middleware automatically:

=over

=item * Creates OpenTelemetry spans for HTTP requests

=item * Extracts W3C trace context from incoming requests

=item * Sets standard HTTP semantic attributes following OpenTelemetry conventions

=item * Handles both synchronous and streaming responses

=item * Records exceptions and sets appropriate span status

=back

The following HTTP attributes are set on spans:

=over

=item * C<http.request.method> - HTTP method

t/otel_integration.t  view on Meta::CPAN

            my $path = $env->{PATH_INFO} || '/';
            
            if ($path eq '/success') {
                return [200, ['Content-Type' => 'text/plain'], ['OK']];
            } elsif ($path eq '/not_found') {
                return [404, ['Content-Type' => 'text/plain'], ['Not Found']];
            } elsif ($path eq '/server_error') {
                return [500, ['Content-Type' => 'text/plain'], ['Server Error']];
            } elsif ($path eq '/exception') {
                die "Test exception message";
            } elsif ($path eq '/streaming') {
                return sub {
                    my $respond = shift;
                    my $writer = $respond->([200, ['Content-Type' => 'text/plain']]);
                    $writer->write("chunk1");
                    $writer->write("chunk2");
                    $writer->close;
                };
            } else {
                return [200, ['Content-Type' => 'text/plain'], ['Default']];
            }

t/otel_integration.t  view on Meta::CPAN

    };
    
    my $span = parse_console_span($span_output);
    is $span->{status}{code}, SPAN_STATUS_ERROR, 'Exception marked span as error';
    is $span->{attributes}->{'http.response.status_code'}, 500, 'Exception sets 500 status';
    
    # Check if exception was recorded
    ok $span->{events} && @{$span->{events}}, 'Exception events recorded';
};

# Test streaming responses
subtest 'Streaming Response' => sub {
    my $app = build_app();
    
    my $span_output = capture_stderr {
        test_psgi $app, sub {
            my $cb = shift;
            my $res = $cb->(GET 'http://example.com/streaming');
            is $res->code, 200;
            is $res->content, 'chunk1chunk2', 'Streaming content received';
        };
    };
    
    # For streaming responses, multiple spans might be output or format might differ
    # Just verify we captured some span output
    ok $span_output, 'Span output captured for streaming response';
    like $span_output, qr/streaming|GET request/, 'Span output contains expected content';
};

# Test resource attributes
subtest 'Resource Attributes' => sub {
    my $app = build_app(
        resource_attributes => {
            'service.name' => 'test-service',
            'service.version' => '1.0.0'
        }
    );



( run in 0.550 second using v1.01-cache-2.11-cpan-4face438c0f )