├── examples ├── quickstart │ ├── .gitignore │ ├── cpanfile │ ├── Dice │ ├── Dockerfile │ └── docker-compose.yaml ├── collector │ ├── prometheus.yaml │ ├── docker-compose.yaml │ ├── collector.yaml │ └── README.md └── instrumentations │ ├── http-tiny │ └── lwp ├── lib ├── OpenTelemetry │ ├── X │ │ ├── Invalid.pm │ │ ├── Parsing.pm │ │ ├── Unsupported.pm │ │ ├── Parsing.pod │ │ ├── Invalid.pod │ │ └── Unsupported.pod │ ├── Integration.pm │ ├── Propagator │ │ ├── None.pm │ │ ├── TextMap.pm │ │ ├── TraceContext │ │ │ ├── TraceFlags.pm │ │ │ ├── TraceFlags.pod │ │ │ ├── TraceParent.pm │ │ │ ├── TraceState.pod │ │ │ ├── TraceState.pm │ │ │ └── TraceParent.pod │ │ ├── None.pod │ │ ├── Baggage.pod │ │ ├── Composite.pm │ │ ├── Composite.pod │ │ ├── TraceContext.pod │ │ ├── TextMap.pod │ │ ├── Baggage.pm │ │ └── TraceContext.pm │ ├── Exporter.pm │ ├── Propagator.pm │ ├── Logs │ │ ├── LogRecord │ │ │ ├── Processor.pm │ │ │ └── Processor.pod │ │ ├── LoggerProvider.pm │ │ ├── Logger.pm │ │ ├── LoggerProvider.pod │ │ └── Logger.pod │ ├── Trace │ │ ├── Span │ │ │ ├── Processor.pm │ │ │ ├── Status.pm │ │ │ ├── Status.pod │ │ │ └── Processor.pod │ │ ├── TracerProvider.pm │ │ ├── Link.pm │ │ ├── Event.pm │ │ ├── Span.pm │ │ ├── SpanContext.pm │ │ ├── Tracer.pm │ │ ├── Link.pod │ │ ├── Event.pod │ │ ├── TracerProvider.pod │ │ └── Tracer.pod │ ├── X.pm │ ├── Integration.pod │ ├── Instrumentation │ │ ├── DBI.pod │ │ └── DBI.pm │ ├── Processor.pm │ ├── Trace.pm │ ├── X.pod │ ├── Processor.pod │ ├── Baggage.pm │ ├── Context.pm │ ├── Common.pm │ ├── Attributes.pod │ ├── Exporter.pod │ ├── Common.pod │ ├── Attributes.pm │ ├── Guides │ │ ├── Libraries.pod │ │ └── Exporters.pod │ ├── Instrumentation.pm │ ├── Propagator.pod │ └── Trace.pod ├── Test2 │ └── Tools │ │ └── OpenTelemetry.pm └── Log │ └── Any │ └── Adapter │ ├── OpenTelemetry.pod │ └── OpenTelemetry.pm ├── .mailmap ├── .gitignore ├── Build.PL ├── t ├── OpenTelemetry │ ├── Logs │ │ ├── Logger.t │ │ └── LoggerProvider.t │ ├── Trace │ │ ├── TracerProvider.t │ │ ├── Event.t │ │ ├── Link.t │ │ ├── Span.t │ │ ├── Span │ │ │ └── Status.t │ │ └── Tracer.t │ ├── X.t │ ├── Propagator │ │ ├── None.t │ │ ├── TraceContext │ │ │ ├── TraceFlags.t │ │ │ ├── TraceParent.t │ │ │ └── TraceState.t │ │ ├── Composite.t │ │ └── Baggage.t │ ├── Common.t │ ├── Trace.t │ ├── Context.t │ ├── Instrumentation.t │ ├── Baggage.t │ └── Constants.t └── Log │ └── Any │ └── Adapter │ └── OpenTelemetry.t ├── dist.ini ├── cpanfile ├── .github └── workflows │ └── test.yml └── README.md /examples/quickstart/.gitignore: -------------------------------------------------------------------------------- 1 | local/ 2 | -------------------------------------------------------------------------------- /lib/OpenTelemetry/X/Invalid.pm: -------------------------------------------------------------------------------- 1 | package OpenTelemetry::X::Invalid; 2 | 3 | our $VERSION = '0.034'; 4 | 5 | use parent 'OpenTelemetry::X'; 6 | 7 | 1; 8 | -------------------------------------------------------------------------------- /lib/OpenTelemetry/X/Parsing.pm: -------------------------------------------------------------------------------- 1 | package OpenTelemetry::X::Parsing; 2 | 3 | our $VERSION = '0.034'; 4 | 5 | use parent 'OpenTelemetry::X'; 6 | 7 | 1; 8 | -------------------------------------------------------------------------------- /lib/OpenTelemetry/X/Unsupported.pm: -------------------------------------------------------------------------------- 1 | package OpenTelemetry::X::Unsupported; 2 | 3 | our $VERSION = '0.034'; 4 | 5 | use parent 'OpenTelemetry::X'; 6 | 7 | 1; 8 | -------------------------------------------------------------------------------- /examples/quickstart/cpanfile: -------------------------------------------------------------------------------- 1 | requires 'Mojolicious'; 2 | requires 'Mojolicious::Plugin::OpenTelemetry'; 3 | requires 'OpenTelemetry'; 4 | requires 'OpenTelemetry::SDK'; 5 | -------------------------------------------------------------------------------- /.mailmap: -------------------------------------------------------------------------------- 1 | José Joaquín Atria 2 | José Joaquín Atria 3 | José Joaquín Atria 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.swp 3 | *.swo 4 | .build/ 5 | cover_db/ 6 | 7 | # Build artifacts 8 | Build 9 | blib/ 10 | MYMETA.* 11 | _build_params 12 | OpenTelemetry-*.tar.gz 13 | OpenTelemetry-*/ 14 | -------------------------------------------------------------------------------- /examples/collector/prometheus.yaml: -------------------------------------------------------------------------------- 1 | scrape_configs: 2 | - job_name: 'collector' 3 | scrape_interval: 10s 4 | static_configs: 5 | - targets: ['collector:8889'] 6 | - targets: ['collector:8888'] 7 | -------------------------------------------------------------------------------- /Build.PL: -------------------------------------------------------------------------------- 1 | # This Build.PL for OpenTelemetry was generated by Dist::Zilla::Plugin::ModuleBuildTiny 0.020. 2 | use strict; 3 | use warnings; 4 | 5 | 6 | use v5.30.0; 7 | use Module::Build::Tiny 0.034; 8 | Build_PL(); 9 | 10 | -------------------------------------------------------------------------------- /lib/OpenTelemetry/Integration.pm: -------------------------------------------------------------------------------- 1 | package OpenTelemetry::Integration; 2 | # ABSTRACT: Support for legacy OpenTelemetry instrumentations 3 | 4 | our $VERSION = '0.034'; 5 | 6 | use parent 'OpenTelemetry::Instrumentation'; 7 | 8 | 1; 9 | -------------------------------------------------------------------------------- /lib/OpenTelemetry/Propagator/None.pm: -------------------------------------------------------------------------------- 1 | package OpenTelemetry::Propagator::None; 2 | # ABSTRACT: A context propagator for OpenTelemetry that does nothing 3 | 4 | our $VERSION = '0.034'; 5 | 6 | use parent 'OpenTelemetry::Propagator::TextMap'; 7 | 8 | 1; 9 | -------------------------------------------------------------------------------- /examples/quickstart/Dice: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | use Mojolicious::Lite -signatures; 3 | 4 | use OpenTelemetry::SDK; 5 | plugin 'OpenTelemetry'; 6 | 7 | get '/roll' => sub ($c) { 8 | $c->render( json => int 1 + rand 6 ); 9 | }; 10 | 11 | app->start; 12 | -------------------------------------------------------------------------------- /examples/quickstart/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM perl:5.40 2 | 3 | WORKDIR /app 4 | ADD cpanfile cpanfile.snapshot ./ 5 | RUN cpm install --local-lib-contained=/root/.local Carton::Snapshot \ 6 | && PERL5OPT="-I/root/.local/lib/perl5" cpm install --global \ 7 | && rm -rf cpanfile cpanfile.snapshot /root/.local 8 | -------------------------------------------------------------------------------- /lib/OpenTelemetry/Exporter.pm: -------------------------------------------------------------------------------- 1 | use Object::Pad; 2 | # ABSTRACT: The abstract interface for OpenTelemetry exporters 3 | 4 | package OpenTelemetry::Exporter; 5 | 6 | our $VERSION = '0.034'; 7 | 8 | role OpenTelemetry::Exporter { 9 | method export; 10 | method shutdown; 11 | method force_flush; 12 | } 13 | -------------------------------------------------------------------------------- /lib/OpenTelemetry/Propagator.pm: -------------------------------------------------------------------------------- 1 | use Object::Pad; 2 | # ABSTRACT: An abstract interface for OpenTelemetry propagators 3 | 4 | package OpenTelemetry::Propagator; 5 | 6 | our $VERSION = '0.034'; 7 | 8 | role OpenTelemetry::Propagator { 9 | method extract; 10 | method inject; 11 | method keys; 12 | } 13 | -------------------------------------------------------------------------------- /examples/quickstart/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | app: 3 | build: 4 | context: . 5 | volumes: 6 | - .:/app 7 | command: 8 | - ./Dice 9 | - daemon 10 | - -l 11 | - http://*:3000 12 | environment: 13 | OTEL_TRACES_EXPORTER: console 14 | ports: 15 | - 3000:3000 16 | -------------------------------------------------------------------------------- /lib/OpenTelemetry/Logs/LogRecord/Processor.pm: -------------------------------------------------------------------------------- 1 | use Object::Pad; 2 | # ABSTRACT: The abstract interface for OpenTelemetry log record processors 3 | 4 | package OpenTelemetry::Logs::LogRecord::Processor; 5 | 6 | our $VERSION = '0.034'; 7 | 8 | role OpenTelemetry::Logs::LogRecord::Processor :does(OpenTelemetry::Processor) { 9 | method on_emit; 10 | } 11 | -------------------------------------------------------------------------------- /lib/OpenTelemetry/Trace/Span/Processor.pm: -------------------------------------------------------------------------------- 1 | use Object::Pad; 2 | # ABSTRACT: The abstract interface for OpenTelemetry span processors 3 | 4 | package OpenTelemetry::Trace::Span::Processor; 5 | 6 | our $VERSION = '0.034'; 7 | 8 | role OpenTelemetry::Trace::Span::Processor :does(OpenTelemetry::Processor) { 9 | method on_start; 10 | method on_end; 11 | } 12 | -------------------------------------------------------------------------------- /t/OpenTelemetry/Logs/Logger.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use Test2::V0 -target => 'OpenTelemetry::Logs::Logger'; 4 | use Test2::Tools::OpenTelemetry; 5 | 6 | is my $logger = CLASS->new, object { 7 | prop isa => $CLASS; 8 | }, 'Can construct logger'; 9 | 10 | is $logger->emit_record( body => 'foo' ), U, 11 | 'Emit record returns nothing'; 12 | 13 | done_testing; 14 | -------------------------------------------------------------------------------- /lib/OpenTelemetry/Logs/LoggerProvider.pm: -------------------------------------------------------------------------------- 1 | use Object::Pad; 2 | # ABSTRACT: Provides access to OpenTelemetry Loggers 3 | 4 | package OpenTelemetry::Logs::LoggerProvider; 5 | 6 | our $VERSION = '0.034'; 7 | 8 | class OpenTelemetry::Logs::LoggerProvider { 9 | use OpenTelemetry::Logs::Logger; 10 | 11 | field $logger; 12 | 13 | method logger ( %args ) { 14 | $logger //= OpenTelemetry::Logs::Logger->new; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/OpenTelemetry/Trace/TracerProvider.pm: -------------------------------------------------------------------------------- 1 | use Object::Pad; 2 | # ABSTRACT: Provides access to OpenTelemetry Tracers 3 | 4 | package OpenTelemetry::Trace::TracerProvider; 5 | 6 | our $VERSION = '0.034'; 7 | 8 | class OpenTelemetry::Trace::TracerProvider { 9 | use OpenTelemetry::Trace::Tracer; 10 | 11 | field $tracer; 12 | 13 | method tracer ( %args ) { 14 | $tracer //= OpenTelemetry::Trace::Tracer->new; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/OpenTelemetry/X.pm: -------------------------------------------------------------------------------- 1 | package OpenTelemetry::X; 2 | 3 | our $VERSION = '0.034'; 4 | 5 | use X::Tiny; 6 | use parent 'X::Tiny::Base'; 7 | 8 | sub to_string { '' . shift->[0] } # Do not print exception type 9 | 10 | sub create { 11 | my $pkg = ref($_[0]) || $_[0]; 12 | 13 | die "The use of $pkg->create is not allowed. Call OpenTelemetry::X->create instead" 14 | unless $pkg eq 'OpenTelemetry::X'; 15 | 16 | goto \&X::Tiny::create; 17 | } 18 | 19 | 1; 20 | -------------------------------------------------------------------------------- /t/OpenTelemetry/Logs/LoggerProvider.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use Test2::V0 -target => 'OpenTelemetry::Logs::LoggerProvider'; 4 | 5 | is my $provider = CLASS->new, object { 6 | prop isa => $CLASS; 7 | }, 'Can construct a provider'; 8 | 9 | is my $logger = $provider->logger, 10 | object { prop isa => 'OpenTelemetry::Logs::Logger' }, 11 | 'Can provide a logger'; 12 | 13 | ref_is $provider->logger, $logger, 14 | 'Provided logger is cached internally'; 15 | 16 | done_testing; 17 | -------------------------------------------------------------------------------- /t/OpenTelemetry/Trace/TracerProvider.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use Test2::V0 -target => 'OpenTelemetry::Trace::TracerProvider'; 4 | 5 | is my $provider = CLASS->new, object { 6 | prop isa => $CLASS; 7 | }, 'Can construct a provider'; 8 | 9 | is my $tracer = $provider->tracer, 10 | object { prop isa => 'OpenTelemetry::Trace::Tracer' }, 11 | 'Can provide a tracer'; 12 | 13 | ref_is $provider->tracer, $tracer, 14 | 'Provided tracer is cached internally'; 15 | 16 | done_testing; 17 | -------------------------------------------------------------------------------- /t/OpenTelemetry/Trace/Event.t: -------------------------------------------------------------------------------- 1 | #!/bin/env perl 2 | 3 | use Test2::V0 -target => 'OpenTelemetry::Trace::Event'; 4 | use Test2::Tools::OpenTelemetry; 5 | 6 | is messages { 7 | is CLASS->new, object { 8 | call name => 'empty'; 9 | call attributes => hash { etc }; # Does this need a getter? 10 | call timestamp => T; 11 | }, 'Can create an event'; 12 | } => [ 13 | [ warning => OpenTelemetry => match qr/Missing name when creating .* event/ ], 14 | ]; 15 | 16 | done_testing; 17 | -------------------------------------------------------------------------------- /t/OpenTelemetry/X.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use Test2::V0; 4 | 5 | use OpenTelemetry::X; 6 | use OpenTelemetry::X::Invalid; 7 | 8 | is my $x = OpenTelemetry::X->create('Invalid'), object { 9 | prop isa => 'OpenTelemetry::X'; 10 | }, 'Created exceptions are children of OpenTelemetry::X'; 11 | 12 | like dies { $x->create('Invalid') }, 13 | qr/OpenTelemetry::X::Invalid->create is not allowed.*OpenTelemetry::X->create/, 14 | 'Only OpenTelemetry::X package can create excetpions'; 15 | 16 | done_testing; 17 | -------------------------------------------------------------------------------- /lib/OpenTelemetry/Logs/Logger.pm: -------------------------------------------------------------------------------- 1 | use Object::Pad; 2 | # ABSTRACT: A log factory for OpenTelemetry 3 | 4 | package OpenTelemetry::Logs::Logger; 5 | 6 | our $VERSION = '0.034'; 7 | 8 | # TODO: Should this implement an interface like that of Mojo::Log 9 | # or Log::Any? It would mean that writing adapters like 10 | # Log::Any::Adapter::OpenTelemetry for other loggers (eg. Log::ger, 11 | # Dancer2::Logger) would be simpler, since the high-level logging 12 | # interface would already exist. I don't think this goes against 13 | # the standard. 14 | class OpenTelemetry::Logs::Logger { 15 | method emit_record ( %args ) { } 16 | } 17 | -------------------------------------------------------------------------------- /lib/OpenTelemetry/Trace/Link.pm: -------------------------------------------------------------------------------- 1 | use Object::Pad ':experimental(init_expr)'; 2 | # ABSTRACT: A link to an OpenTelemetry span 3 | 4 | package OpenTelemetry::Trace::Link; 5 | 6 | our $VERSION = '0.034'; 7 | 8 | class OpenTelemetry::Trace::Link :does(OpenTelemetry::Attributes) { 9 | use OpenTelemetry::X; 10 | 11 | use isa 'OpenTelemetry::Trace::SpanContext'; 12 | 13 | field $context :param :reader; 14 | 15 | ADJUST { 16 | die OpenTelemetry::X->create( 17 | Invalid => "Required parameter 'context' must be a span context" 18 | ) unless isa_OpenTelemetry_Trace_SpanContext $context; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/OpenTelemetry/Trace/Event.pm: -------------------------------------------------------------------------------- 1 | use Object::Pad ':experimental(init_expr)'; 2 | # ABSTRACT: An event in an OpenTelemetry span 3 | 4 | package OpenTelemetry::Trace::Event; 5 | 6 | our $VERSION = '0.034'; 7 | 8 | class OpenTelemetry::Trace::Event :does(OpenTelemetry::Attributes) { 9 | use Time::HiRes; 10 | use OpenTelemetry::Common (); 11 | 12 | my $logger = OpenTelemetry::Common::internal_logger; 13 | 14 | field $name :param :reader = undef; 15 | field $timestamp :param :reader //= Time::HiRes::time; 16 | 17 | ADJUST { 18 | $name //= do { 19 | $logger->warn("Missing name when creating a span event. Setting to 'empty'"); 20 | 'empty'; 21 | }; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/OpenTelemetry/Integration.pod: -------------------------------------------------------------------------------- 1 | =encoding utf8 2 | 3 | =head1 NAME 4 | 5 | OpenTelemetry::Integration - Support for legacy OpenTelemetry instrumentations 6 | 7 | =head1 DESCRIPTION 8 | 9 | The namespace for OpenTelemetry instrumentation libraries has been renamed 10 | to L. Please refer to that document instead. 11 | 12 | This module is kept here so that existing code that uses the old name 13 | continues to work, but new code should not use this module. 14 | 15 | =head1 COPYRIGHT 16 | 17 | This software is copyright (c) 2024 by José Joaquín Atria. 18 | 19 | This is free software; you can redistribute it and/or modify it under the same 20 | terms as the Perl 5 programming language system itself. 21 | -------------------------------------------------------------------------------- /t/OpenTelemetry/Trace/Link.t: -------------------------------------------------------------------------------- 1 | #!/bin/env perl 2 | 3 | use Test2::V0 -target => 'OpenTelemetry::Trace::Link'; 4 | 5 | use Scalar::Util 'refaddr'; 6 | use OpenTelemetry::Trace::SpanContext; 7 | 8 | my $context = OpenTelemetry::Trace::SpanContext::INVALID; 9 | 10 | is CLASS->new( context => $context ), object { 11 | call attributes => hash { etc }; # Does this need a getter? 12 | call context => validator sub { refaddr $_ == refaddr $context }; 13 | }, 'Can create a link'; 14 | 15 | like dies { CLASS->new }, 16 | qr/^Required parameter 'context' is missing/, 17 | 'Requires a context'; 18 | 19 | like dies { CLASS->new( context => mock ) }, 20 | qr/^Required parameter 'context' must be a span context/, 21 | 'Validates context'; 22 | 23 | done_testing; 24 | -------------------------------------------------------------------------------- /lib/OpenTelemetry/Instrumentation/DBI.pod: -------------------------------------------------------------------------------- 1 | =encoding utf8 2 | 3 | =head1 NAME 4 | 5 | OpenTelemetry::Instrumentation::DBI - OpenTelemetry instrumentation for DBI 6 | 7 | =head1 SYNOPSIS 8 | 9 | use OpenTelemetry::Instrumentation 'DBI'; 10 | my $dbh = DBI->connect(...); 11 | my $result = $dbh->selectall_hashref($statement); 12 | 13 | =head1 DESCRIPTION 14 | 15 | See L for more details. 16 | 17 | Since this is a core module, it's included in the L core 18 | distribution as well. 19 | 20 | =head1 COPYRIGHT 21 | 22 | This software is copyright (c) 2023 by José Joaquín Atria. 23 | 24 | This is free software; you can redistribute it and/or modify it under the same 25 | terms as the Perl 5 programming language system itself. 26 | -------------------------------------------------------------------------------- /examples/collector/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | collector: 3 | image: otel/opentelemetry-collector 4 | ports: 5 | - 8888:8888 # Prometheus metrics exposed by the collector 6 | - 8889:8889 # Prometheus exporter metrics 7 | - 4317:4317 # OTLP gRPC receiver 8 | - 4318:4318 # OTLP HTTP receiver 9 | volumes: 10 | - ./collector.yaml:/etc/otelcol/config.yaml 11 | depends_on: 12 | - jaeger 13 | - prometheus 14 | 15 | jaeger: 16 | image: jaegertracing/all-in-one:1.60 17 | ports: 18 | - 16686:16686 19 | environment: 20 | - METRICS_STORAGE_TYPE=prometheus 21 | 22 | prometheus: 23 | image: prom/prometheus 24 | volumes: 25 | - ./prometheus.yaml:/etc/prometheus/prometheus.yml 26 | ports: 27 | - 9090:9090 28 | -------------------------------------------------------------------------------- /lib/OpenTelemetry/X/Parsing.pod: -------------------------------------------------------------------------------- 1 | =encoding UTF-8 2 | 3 | =head1 NAME 4 | 5 | OpenTelemetry::X::Parsing - Unable to parse data from a payload 6 | 7 | =head1 SYNOPSIS 8 | 9 | use OpenTelemetry::X; 10 | 11 | die OpenTelemetry::X->create( Parsing => $message ); 12 | 13 | =head1 DESCRIPTION 14 | 15 | Raised when an attempt to parse data from a payload fails. 16 | 17 | You should not be manually creating instances of this class. See 18 | L for details on how to create instances of this class. 19 | 20 | =head1 SEE ALSO 21 | 22 | =over 23 | 24 | =item L 25 | 26 | =back 27 | 28 | =head1 COPYRIGHT AND LICENSE 29 | 30 | This software is copyright (c) 2023 by José Joaquín Atria. 31 | 32 | This is free software; you can redistribute it and/or modify it under the same 33 | terms as the Perl 5 programming language system itself. 34 | -------------------------------------------------------------------------------- /lib/OpenTelemetry/X/Invalid.pod: -------------------------------------------------------------------------------- 1 | =encoding UTF-8 2 | 3 | =head1 NAME 4 | 5 | OpenTelemetry::X::Invalid - Invalid arguments used in an OpenTelemetry operation 6 | 7 | =head1 SYNOPSIS 8 | 9 | use OpenTelemetry::X; 10 | 11 | die OpenTelemetry::X->create( Invalid => $message ); 12 | 13 | =head1 DESCRIPTION 14 | 15 | Represents invalid arguments used in an OpenTelemetry operation. 16 | 17 | You should not be manually creating instances of this class. See 18 | L for details on how to create instances of this class. 19 | 20 | =head1 SEE ALSO 21 | 22 | =over 23 | 24 | =item L 25 | 26 | =back 27 | 28 | =head1 COPYRIGHT AND LICENSE 29 | 30 | This software is copyright (c) 2023 by José Joaquín Atria. 31 | 32 | This is free software; you can redistribute it and/or modify it under the same 33 | terms as the Perl 5 programming language system itself. 34 | -------------------------------------------------------------------------------- /t/OpenTelemetry/Propagator/None.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use Test2::V0 -target => 'OpenTelemetry::Propagator::None'; 4 | use Test2::Tools::OpenTelemetry; 5 | 6 | use OpenTelemetry::Context; 7 | 8 | my $root = OpenTelemetry::Context->current; 9 | 10 | my $carrier = {}; 11 | 12 | is my $prop = CLASS->new, object { 13 | prop isa => $CLASS; 14 | }, 'Can construct propagator'; 15 | 16 | no_messages { 17 | ref_is $prop->inject( $carrier, $root, sub { die } ), $prop, 18 | 'Propagator does nothing'; 19 | 20 | is $carrier, {}, 'Nothing injected to carrier'; 21 | 22 | ref_is $prop->extract( $carrier, $root, sub { die } ), $root, 23 | 'Propagator does nothing'; 24 | 25 | ref_is $prop->extract( $carrier, undef, sub { die } ), $root, 26 | 'Undef context defaults to current'; 27 | 28 | is [ $prop->keys ], [], 'No keys'; 29 | }; 30 | 31 | done_testing; 32 | -------------------------------------------------------------------------------- /lib/OpenTelemetry/Processor.pm: -------------------------------------------------------------------------------- 1 | use Object::Pad; 2 | # ABSTRACT: The abstract interface for OpenTelemetry processors 3 | 4 | package OpenTelemetry::Processor; 5 | 6 | our $VERSION = '0.034'; 7 | 8 | # NOTE: Moving this here creates a nice symmetry where we have 9 | # OpenTelemetry::{Propagator,Processor,Exporter} at the top-level 10 | # and allow for specific implementations to live under them. 11 | # We should decide where we expect implementations that are 12 | # specific to Traces / Logs / Metrics should live, though. 13 | # For now, this ends up giving us 14 | # * OpenTelemetry::Trace::Span::Processor 15 | # * OpenTelemetry::Logs::LogRecord::Processor 16 | # * OpenTelemetry::Metrics::Instrument::Processor (hypothetical) 17 | # and the SDK implementations with `::SDK` in there somewhere. 18 | role OpenTelemetry::Processor { 19 | method process; 20 | method shutdown; 21 | method force_flush; 22 | } 23 | -------------------------------------------------------------------------------- /lib/OpenTelemetry/X/Unsupported.pod: -------------------------------------------------------------------------------- 1 | =encoding UTF-8 2 | 3 | =head1 NAME 4 | 5 | OpenTelemetry::X::Unsupported - Attempted to use an unsupported version 6 | 7 | =head1 SYNOPSIS 8 | 9 | use OpenTelemetry::X; 10 | 11 | die OpenTelemetry::X->create( Unsupported => $message ); 12 | 13 | =head1 DESCRIPTION 14 | 15 | Raised when an operation encountered a request to use a version that is not 16 | supported. 17 | 18 | You should not be manually creating instances of this class. See 19 | L for details on how to create instances of this class. 20 | 21 | =head1 SEE ALSO 22 | 23 | =over 24 | 25 | =item L 26 | 27 | =back 28 | 29 | =head1 COPYRIGHT AND LICENSE 30 | 31 | This software is copyright (c) 2023 by José Joaquín Atria. 32 | 33 | This is free software; you can redistribute it and/or modify it under the same 34 | terms as the Perl 5 programming language system itself. 35 | -------------------------------------------------------------------------------- /dist.ini: -------------------------------------------------------------------------------- 1 | name = OpenTelemetry 2 | author = José Joaquín Atria 3 | license = Perl_5 4 | copyright_holder = José Joaquín Atria 5 | copyright_year = 2022 6 | 7 | [@Starter::Git] 8 | -remove = Pod2Readme 9 | -remove = Git::Push 10 | installer = ModuleBuildTiny 11 | regenerate = Build.PL 12 | regenerate = META.json 13 | regenerate = LICENSE 14 | revision = 5 15 | managed_versions = 1 16 | Git::GatherDir.exclude_filename[0] = dist.ini 17 | Git::GatherDir.exclude_filename[1] = cpanfile 18 | Git::Tag.tag_message = 19 | Release_Commit.commit_msg = Release v%V%t 20 | RewriteVersion.global = 1 21 | 22 | [MinimumPerl] 23 | perl = v5.30.0 24 | 25 | [Prereqs::FromCPANfile] 26 | 27 | [Repository] 28 | [Bugtracker] 29 | web = https://github.com/jjatria/perl-opentelemetry/issues 30 | 31 | [Git::Contributors] 32 | [Meta::Contributors] 33 | contributor = Tom Molesworth 34 | -------------------------------------------------------------------------------- /lib/OpenTelemetry/Trace/Span.pm: -------------------------------------------------------------------------------- 1 | use Object::Pad ':experimental(init_expr)'; 2 | # ABSTRACT: A single operation within a trace 3 | 4 | package OpenTelemetry::Trace::Span; 5 | 6 | use OpenTelemetry::Trace::SpanContext; 7 | 8 | our $VERSION = '0.034'; 9 | 10 | class OpenTelemetry::Trace::Span { 11 | field $context :param :reader //= OpenTelemetry::Trace::SpanContext->new; 12 | 13 | method add_event ( %args ) { $self } 14 | 15 | method end ( $timestamp = time ) { $self } 16 | 17 | method record_exception ( $exception, %attributes ) { $self } 18 | 19 | method recording { 0 } 20 | 21 | method set_attribute ( %args ) { $self } 22 | 23 | method set_name ( $name ) { $self } 24 | 25 | method set_status ( $status, $description = '' ) { $self } 26 | } 27 | 28 | use constant { 29 | INVALID => OpenTelemetry::Trace::Span->new( 30 | context => OpenTelemetry::Trace::SpanContext::INVALID, 31 | ), 32 | }; 33 | 34 | 1; 35 | -------------------------------------------------------------------------------- /lib/OpenTelemetry/Propagator/TextMap.pm: -------------------------------------------------------------------------------- 1 | use Object::Pad; 2 | # ABSTRACT: A context propagator for OpenTelemetry using string key / value pairs 3 | 4 | use experimental 'signatures'; 5 | 6 | package OpenTelemetry::Propagator::TextMap; 7 | 8 | our $VERSION = '0.034'; 9 | 10 | sub SETTER { 11 | sub ( $carrier, $key, $value ) { $carrier->{$key} = $value; return } 12 | } 13 | 14 | sub GETTER { 15 | sub ( $carrier, $key ) { $carrier->{$key} } 16 | } 17 | 18 | class OpenTelemetry::Propagator::TextMap :does(OpenTelemetry::Propagator) { 19 | use OpenTelemetry::Context; 20 | 21 | method inject ( 22 | $carrier, 23 | $context = undef, 24 | $setter = undef 25 | ) { 26 | return $self; 27 | } 28 | 29 | method extract ( 30 | $carrier, 31 | $context = undef, 32 | $getter = undef 33 | ) { 34 | return $context // OpenTelemetry::Context->current; 35 | } 36 | 37 | method keys () { } 38 | } 39 | -------------------------------------------------------------------------------- /cpanfile: -------------------------------------------------------------------------------- 1 | requires 'isa'; # To support perls older than 5.32 2 | requires 'Bytes::Random::Secure'; 3 | requires 'Carp::Clan'; 4 | requires 'Class::Method::Modifiers'; 5 | requires 'Exporter::Tiny', '0.044'; # For -as => CODE support 6 | requires 'Feature::Compat::Defer'; 7 | requires 'Feature::Compat::Try', '0.05'; # For finally before 5.35 8 | requires 'Future', '0.26'; # For Future->done 9 | requires 'Future::AsyncAwait'; 10 | requires 'List::Util', '1.45'; # For uniq 11 | requires 'Log::Any'; 12 | requires 'Module::Pluggable'; 13 | requires 'Module::Runtime'; 14 | requires 'Mutex'; 15 | requires 'Object::Pad', '0.74'; # For //= field initialisers 16 | requires 'Ref::Util'; 17 | requires 'Sentinel'; 18 | requires 'Syntax::Keyword::Dynamically'; 19 | requires 'URI'; 20 | requires 'URL::Encode'; 21 | requires 'UUID::URandom'; 22 | requires 'X::Tiny'; 23 | 24 | on test => sub { 25 | requires 'Class::Inspector'; # For OpenTelemetry::Instrumentation test 26 | requires 'Metrics::Any'; 27 | requires 'Test2::V0'; 28 | }; 29 | -------------------------------------------------------------------------------- /examples/collector/collector.yaml: -------------------------------------------------------------------------------- 1 | # https://opentelemetry.io/docs/collector/configuration/#receivers 2 | receivers: 3 | otlp: 4 | protocols: 5 | grpc: 6 | endpoint: 0.0.0.0:4317 7 | http: 8 | endpoint: 0.0.0.0:4318 9 | 10 | # https://opentelemetry.io/docs/collector/configuration/#processors 11 | processors: 12 | batch: 13 | 14 | # https://opentelemetry.io/docs/collector/configuration/#exporters 15 | exporters: 16 | prometheus: 17 | endpoint: 0.0.0.0:8889 18 | 19 | otlp: 20 | endpoint: http://jaeger:4317 21 | tls: 22 | insecure: true 23 | 24 | debug: 25 | verbosity: detailed 26 | 27 | # https://opentelemetry.io/docs/collector/configuration/#service 28 | service: 29 | pipelines: 30 | traces: 31 | receivers: [otlp] 32 | processors: [batch] 33 | exporters: [debug, otlp] 34 | metrics: 35 | receivers: [otlp] 36 | processors: [batch] 37 | exporters: [debug, otlp] 38 | logs: 39 | receivers: [otlp] 40 | exporters: [debug, otlp] 41 | -------------------------------------------------------------------------------- /examples/collector/README.md: -------------------------------------------------------------------------------- 1 | # OpenTelemetry Collector Demo 2 | 3 | This example defines a simple observability stack made up of an OpenTelemetry 4 | Collector instance connected to a Jaeger and a Prometheus backend. Since this 5 | will mostly be used for debugging purposes, the collector is also configured 6 | to export any received telemetry to the console. 7 | 8 | This example is based on the excellent demo available in the 9 | [opentelemetry-collector-contrib] repository. 10 | 11 | The example uses docker compose. In order to use it, make a local copy of this 12 | repository and run the following command: 13 | 14 | docker compose up 15 | 16 | You should then be able to see the following backends: 17 | 18 | - Jaeger: http://localhost:16686 19 | - Prometheus: http://localhost:9090 20 | 21 | For a more full example that uses this stack please refer to the original 22 | example, or to [OpenTelemetry::Guides::Exporter] which uses the Perl SDK. 23 | 24 | [opentelemetry-collector-contrib]: https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/7e3d00326a919ccf053f90c0a61f057b5b0d450a/examples/demo 25 | [opentelemetry::guides::exporter]: https://metacpan.org/pod/OpenTelemetry::Guides::Exporter 26 | -------------------------------------------------------------------------------- /lib/OpenTelemetry/Propagator/TraceContext/TraceFlags.pm: -------------------------------------------------------------------------------- 1 | use Object::Pad ':experimental(init_expr)'; 2 | # ABSTRACT: Represents TraceFlags in a W3C TraceContext 3 | 4 | package OpenTelemetry::Propagator::TraceContext::TraceFlags; 5 | 6 | our $VERSION = '0.034'; 7 | 8 | class OpenTelemetry::Propagator::TraceContext::TraceFlags { 9 | use OpenTelemetry::Common (); 10 | my $logger = OpenTelemetry::Common::internal_logger; 11 | 12 | field $flags :param :reader = 0; 13 | 14 | sub BUILDARGS ( $class, $flags = undef ) { 15 | $flags //= 0; 16 | 17 | if ( $flags !~ /^\d+$/a ) { 18 | $logger->warn( 19 | 'Non-numeric value when creating TraceFlags', 20 | { value => $flags }, 21 | ); 22 | $flags = 0; 23 | } 24 | 25 | if ( 0 > $flags || $flags > 255 ) { 26 | $logger->warn( 27 | 'Out-of-range value when creating TraceFlags', 28 | { value => $flags }, 29 | ); 30 | $flags = 0; 31 | } 32 | 33 | ( flags => $flags ); 34 | } 35 | 36 | method to_string () { sprintf '%02x', $flags } 37 | 38 | method sampled () { !!( $flags & 1 ) } 39 | } 40 | -------------------------------------------------------------------------------- /lib/OpenTelemetry/Propagator/None.pod: -------------------------------------------------------------------------------- 1 | =encoding UTF-8 2 | 3 | =head1 NAME 4 | 5 | OpenTelemetry::Propagator::None - A context propagator for OpenTelemetry that does nothing 6 | 7 | =head1 SYNOPSIS 8 | 9 | use OpenTelemetry::Propagator::None; 10 | 11 | my $propagator = OpenTelemetry::Propagator::None->new; 12 | 13 | # Does nothing :( 14 | my $carrier = {}; 15 | $propagator->inject( $carrier, $context ); 16 | 17 | # Still nothing :( 18 | my $new_context = $propagator->extract( $carrier, $context ); 19 | 20 | =head1 DESCRIPTION 21 | 22 | This package defines a no-op propagator class that implements the 23 | L interface. 24 | 25 | =head1 METHODS 26 | 27 | =head2 new 28 | 29 | $propagator = OpenTelemetry::Propagator::None->new 30 | 31 | Constructs a new instance of this propagator. This propagator will do nothing. 32 | 33 | =head1 SEE ALSO 34 | 35 | =over 36 | 37 | =item L 38 | 39 | =item L 40 | 41 | =back 42 | 43 | =head1 COPYRIGHT AND LICENSE 44 | 45 | This software is copyright (c) 2023 by José Joaquín Atria. 46 | 47 | This is free software; you can redistribute it and/or modify it under the same 48 | terms as the Perl 5 programming language system itself. 49 | -------------------------------------------------------------------------------- /lib/OpenTelemetry/Trace/Span/Status.pm: -------------------------------------------------------------------------------- 1 | use Object::Pad ':experimental(init_expr)'; 2 | # ABSTRACT: The status of an OpenTelemetry span 3 | 4 | package OpenTelemetry::Trace::Span::Status; 5 | 6 | our $VERSION = '0.034'; 7 | 8 | class OpenTelemetry::Trace::Span::Status { 9 | use OpenTelemetry::Common (); 10 | use OpenTelemetry::Constants 11 | -span_status => { -as => sub { shift =~ s/^SPAN_STATUS_//r } }; 12 | 13 | my $logger = OpenTelemetry::Common::internal_logger; 14 | 15 | field $code :param :reader = UNSET; 16 | field $description :param :reader = undef; 17 | 18 | ADJUST { 19 | $code = UNSET if $code && $code < UNSET || $code > ERROR; 20 | 21 | if ( $code != ERROR && $description ) { 22 | undef $description; 23 | $logger->warn('Ignoring description on a non-error span status'); 24 | } 25 | 26 | $description //= ''; 27 | } 28 | 29 | sub ok ( $class, %args ) { $class->new( %args, code => OK ) } 30 | sub error ( $class, %args ) { $class->new( %args, code => ERROR ) } 31 | sub unset ( $class, %args ) { $class->new( %args, code => UNSET ) } 32 | 33 | method is_ok () { $code == OK } 34 | method is_error () { $code == ERROR } 35 | method is_unset () { $code == UNSET } 36 | } 37 | -------------------------------------------------------------------------------- /t/OpenTelemetry/Trace/Span.t: -------------------------------------------------------------------------------- 1 | #!/bin/env perl 2 | 3 | use Test2::V0 -target => 'OpenTelemetry::Trace::Span'; 4 | 5 | is my $s = CLASS->new( name => 'foo' ), object { prop isa => CLASS }, 6 | 'Can create span'; 7 | 8 | ref_is $s->add_event( x => 1), $s, 'add_event is chainable'; 9 | ref_is $s->add_event, $s, 'add_event call can be empty'; 10 | ref_is $s->end(123), $s, 'end takes time'; 11 | ref_is $s->end, $s, 'end is chainable'; 12 | ref_is $s->record_exception( 1, a => 1 ), $s, 'record_exception takes pairs'; 13 | ref_is $s->record_exception(1), $s, 'record_exception takes error'; 14 | ref_is $s->set_attribute, $s, 'set_attribute call can be empty'; 15 | ref_is $s->set_attribute( x => 1 ), $s, 'set_attribute is chainable'; 16 | ref_is $s->set_name('bar'), $s, 'set_name is chainable'; 17 | ref_is $s->set_status(0), $s, 'set_status is chainable'; 18 | ref_is $s->set_status(0, 'what'), $s, 'set_status takes description'; 19 | 20 | like dies { $s->set_name }, 21 | qr/^Too few arguments/, 22 | 'set_name requires arguments'; 23 | 24 | like dies { $s->set_status }, 25 | qr/^Too few arguments/, 26 | 'set_status requires arguments'; 27 | 28 | like dies { $s->record_exception }, 29 | qr/^Too few arguments/, 30 | 'record_exception requires arguments'; 31 | 32 | done_testing; 33 | -------------------------------------------------------------------------------- /lib/OpenTelemetry/Trace.pm: -------------------------------------------------------------------------------- 1 | package OpenTelemetry::Trace; 2 | # ABSTRACT: Generic methods for the OpenTelemetry Tracing API 3 | 4 | our $VERSION = '0.034'; 5 | 6 | use strict; 7 | use warnings; 8 | use experimental 'signatures'; 9 | 10 | use OpenTelemetry::Common; 11 | use OpenTelemetry::Context; 12 | use OpenTelemetry::Trace::Span; 13 | 14 | my $current_span_key = OpenTelemetry::Context->key('current-span'); 15 | 16 | sub span_from_context ( $, $context = undef ) { 17 | $context //= OpenTelemetry::Context->current; 18 | $context->get( $current_span_key ) // OpenTelemetry::Trace::Span::INVALID; 19 | } 20 | 21 | sub context_with_span ( $, $span, $context = undef ) { 22 | $context //= OpenTelemetry::Context->current; 23 | $context->set( $current_span_key => $span ); 24 | } 25 | 26 | sub non_recording_span ( $, $context = undef ) { 27 | OpenTelemetry::Trace::Span->new( context => $context ); 28 | } 29 | 30 | sub generate_trace_id { goto \&OpenTelemetry::Common::generate_trace_id } 31 | sub generate_span_id { goto \&OpenTelemetry::Common::generate_span_id } 32 | 33 | { 34 | my $untraced_key = OpenTelemetry::Context->key('untraced'); 35 | sub untraced_context ( $, $context = undef ) { 36 | ( $context // OpenTelemetry::Context->current )->set( $untraced_key => 1 ); 37 | } 38 | 39 | sub is_untraced_context ( $, $context = undef ) { 40 | !! ( $context // OpenTelemetry::Context->current )->get( $untraced_key ); 41 | } 42 | } 43 | 44 | 1; 45 | -------------------------------------------------------------------------------- /t/OpenTelemetry/Propagator/TraceContext/TraceFlags.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use Test2::V0 -target => 'OpenTelemetry::Propagator::TraceContext::TraceFlags'; 4 | use Test2::Tools::OpenTelemetry; 5 | 6 | no_messages { 7 | is CLASS->new(1), object { 8 | call flags => 1; 9 | call sampled => T; 10 | }, 'Can create from number'; 11 | 12 | is CLASS->new(0), object { 13 | call flags => 0; 14 | call sampled => F; 15 | }, 'New with explicit 0'; 16 | 17 | is CLASS->new, object { 18 | call flags => 0; 19 | call sampled => F; 20 | }, 'Defaults to zero'; 21 | 22 | is CLASS->new(undef), object { 23 | call flags => 0; 24 | call sampled => F; 25 | }, 'Ignores undef'; 26 | }; 27 | 28 | is messages { 29 | is CLASS->new('deadbeef'), object { 30 | call flags => 0; 31 | call sampled => F; 32 | }, 'Non-numeric flags ignored'; 33 | 34 | is CLASS->new(-1), object { 35 | call flags => 0; 36 | call sampled => F; 37 | }, 'Ignores negative values'; 38 | 39 | is CLASS->new(256), object { 40 | call flags => 0; 41 | call sampled => F; 42 | }, 'Ignores values above 255'; 43 | } => [ 44 | [ warning => OpenTelemetry => match qr/Non-numeric value/ ], 45 | [ warning => OpenTelemetry => match qr/Non-numeric value/ ], 46 | [ warning => OpenTelemetry => match qr/Out-of-range value/ ], 47 | ], 'Logged invalid values'; 48 | 49 | done_testing; 50 | -------------------------------------------------------------------------------- /lib/Test2/Tools/OpenTelemetry.pm: -------------------------------------------------------------------------------- 1 | package 2 | Test2::Tools::OpenTelemetry; 3 | 4 | our $VERSION = '0.034'; 5 | 6 | use Exporter 'import'; 7 | our @EXPORT = qw( 8 | messages no_messages 9 | metrics no_metrics 10 | ); 11 | 12 | use Feature::Compat::Defer; 13 | use Test2::API 'context'; 14 | use Test2::Compare qw( compare strict_convert ); 15 | 16 | # TODO: Cannot lexically set a metrics adapter 17 | use Metrics::Any::Adapter 'Test'; 18 | 19 | require Log::Any::Adapter; 20 | 21 | my $messages = sub { 22 | my $code = shift; 23 | 24 | Log::Any::Adapter->set( 25 | { lexically => \my $guard }, 26 | Capture => to => \my @messages, 27 | ); 28 | 29 | $code->(); 30 | 31 | \@messages; 32 | }; 33 | 34 | my $metrics = sub { 35 | my $code = shift; 36 | 37 | Metrics::Any::Adapter::Test->clear; 38 | defer { Metrics::Any::Adapter::Test->clear } 39 | 40 | $code->(); 41 | 42 | [ split /\n/, Metrics::Any::Adapter::Test->metrics ]; 43 | }; 44 | 45 | my $no = sub { 46 | my ( $code, $capture, $name ) = @_; 47 | 48 | my $context = context; 49 | my $data = $code->$capture; 50 | my $delta = compare $data, [], \&strict_convert; 51 | 52 | return $context->pass_and_release($name) unless $delta; 53 | $context->fail_and_release( $name, $delta->diag ); 54 | }; 55 | 56 | sub messages (&) { goto $messages } 57 | sub metrics (&) { goto $metrics } 58 | 59 | sub no_messages (&) { shift->$no( $messages, 'No messages logged' ) } 60 | sub no_metrics (&) { shift->$no( $metrics, 'No metrics collected' ) } 61 | 62 | 1; 63 | -------------------------------------------------------------------------------- /lib/OpenTelemetry/Trace/SpanContext.pm: -------------------------------------------------------------------------------- 1 | use Object::Pad ':experimental(init_expr)'; 2 | # ABSTRACT: The part of an OpenTelemetry span that propagates 3 | 4 | package OpenTelemetry::Trace::SpanContext; 5 | 6 | our $VERSION = '0.034'; 7 | 8 | use OpenTelemetry::Common; 9 | use OpenTelemetry::Propagator::TraceContext::TraceFlags; 10 | use OpenTelemetry::Propagator::TraceContext::TraceState; 11 | 12 | class OpenTelemetry::Trace::SpanContext { 13 | use OpenTelemetry::Constants qw( 14 | INVALID_TRACE_ID 15 | INVALID_SPAN_ID 16 | ); 17 | 18 | field $trace_flags :param :reader = undef; 19 | field $trace_state :param :reader = undef; 20 | field $trace_id :param :reader = undef; 21 | field $span_id :param :reader = undef; 22 | field $remote :param :reader = 0; 23 | 24 | BUILD { 25 | $trace_flags //= OpenTelemetry::Propagator::TraceContext::TraceFlags->new; 26 | $trace_state //= OpenTelemetry::Propagator::TraceContext::TraceState->new; 27 | $trace_id //= OpenTelemetry::Common->generate_trace_id; 28 | $span_id //= OpenTelemetry::Common->generate_span_id; 29 | } 30 | 31 | method valid () { 32 | $trace_id && $trace_id ne INVALID_TRACE_ID 33 | && $span_id && $span_id ne INVALID_SPAN_ID; 34 | } 35 | 36 | method hex_trace_id () { unpack 'H*', $trace_id } 37 | method hex_span_id () { unpack 'H*', $span_id } 38 | } 39 | 40 | use constant INVALID => OpenTelemetry::Trace::SpanContext->new( 41 | trace_id => INVALID_TRACE_ID, 42 | span_id => INVALID_SPAN_ID, 43 | ); 44 | -------------------------------------------------------------------------------- /lib/OpenTelemetry/Propagator/Baggage.pod: -------------------------------------------------------------------------------- 1 | =encoding UTF-8 2 | 3 | =head1 NAME 4 | 5 | OpenTelemetry::Propagator::Baggage - Propagate context using the W3C Baggage format 6 | 7 | =head1 SYNOPSIS 8 | 9 | use OpenTelemetry::Baggage; 10 | use OpenTelemetry::Propagator::Baggage; 11 | 12 | my $propagator = OpenTelemetry::Propagator::Baggage; 13 | 14 | # Inject baggage data from the context to a carrier 15 | my $carrier = {}; 16 | $propagator->inject( $carrier, $context ); 17 | 18 | # Extract baggage data from a carrier to the context 19 | my $new_context = $propagator->extract( $carrier, $context ); 20 | 21 | # The baggage data will be in the context 22 | my $baggage = OpenTelemetry::Baggage->all($new_context); 23 | 24 | =head1 DESCRIPTION 25 | 26 | This package defines a propagator class that can interact with the context 27 | (which can be either an implicit or explicit instance of 28 | L) and inject or extract data using the 29 | L. 30 | 31 | It implements the propagator interface defined in 32 | L. 33 | 34 | =head1 SEE ALSO 35 | 36 | =over 37 | 38 | =item L 39 | 40 | =item L 41 | 42 | =item L 43 | 44 | =item L 45 | 46 | =back 47 | 48 | =head1 COPYRIGHT AND LICENSE 49 | 50 | This software is copyright (c) 2023 by José Joaquín Atria. 51 | 52 | This is free software; you can redistribute it and/or modify it under the same 53 | terms as the Perl 5 programming language system itself. 54 | -------------------------------------------------------------------------------- /lib/Log/Any/Adapter/OpenTelemetry.pod: -------------------------------------------------------------------------------- 1 | =encoding UTF-8 2 | 3 | =head1 NAME 4 | 5 | Log::Any::Adapter::OpenTelemetry - Send Log::Any logs to OpenTelemetry 6 | 7 | =head1 SYNOPSIS 8 | 9 | use Log::Any::Adapter 'OpenTelemetry'; 10 | 11 | =head1 DESCRIPTION 12 | 13 | This L plugin configures your logger to generate L 14 | log data using the configured L. 15 | 16 | L log levels are mapped to L log levels as follows: 17 | 18 | =over 19 | 20 | =item C maps to L 21 | 22 | =item C maps to L 23 | 24 | =item C and C map to L 25 | 26 | =item C maps to L 27 | 28 | =item C maps to L 29 | 30 | =item C, C, and C map to L 31 | 32 | =back 33 | 34 | When determining whether a log level is enabled or not, the value 35 | of C will be read from the environment, or that of 36 | C if the former is not set. If neither is set, 37 | or if that variable is set to an unknown value, the default value 38 | will be "info". 39 | 40 | =head1 ACKNOWLEDGEMENTS 41 | 42 | Special thanks to L for their 43 | support in the development of this library. 44 | 45 | =head1 COPYRIGHT AND LICENSE 46 | 47 | This software is copyright (c) 2023 by José Joaquín Atria. 48 | 49 | This is free software; you can redistribute it and/or modify it under the same 50 | terms as the Perl 5 programming language system itself. 51 | -------------------------------------------------------------------------------- /lib/OpenTelemetry/Trace/Tracer.pm: -------------------------------------------------------------------------------- 1 | use Object::Pad; 2 | # ABSTRACT: A span factory for OpenTelemetry 3 | 4 | package OpenTelemetry::Trace::Tracer; 5 | 6 | our $VERSION = '0.034'; 7 | 8 | class OpenTelemetry::Trace::Tracer { 9 | use Feature::Compat::Try; 10 | use Syntax::Keyword::Dynamically; 11 | use Ref::Util 'is_coderef'; 12 | 13 | use OpenTelemetry::Constants qw( SPAN_STATUS_ERROR SPAN_STATUS_OK ); 14 | use OpenTelemetry::Context; 15 | use OpenTelemetry::Trace::Span; 16 | use OpenTelemetry::Trace; 17 | use OpenTelemetry::X; 18 | 19 | method create_span ( %args ) { 20 | OpenTelemetry::Trace::Span::INVALID; 21 | } 22 | 23 | # Experimental 24 | method in_span { 25 | is_coderef $_[-1] or die OpenTelemetry::X->create( 26 | Invalid => 'Missing required code block in call to Tracer->in_span', 27 | ); 28 | 29 | my $block = pop; 30 | my $name = shift; 31 | my %args = @_; 32 | 33 | $args{name} = $name or die OpenTelemetry::X->create( 34 | Invalid => 'Missing required span name to Tracer->in_span', 35 | ); 36 | 37 | my $span = $self->create_span( 38 | %args, 39 | parent => OpenTelemetry::Context->current 40 | ); 41 | 42 | my $context = OpenTelemetry::Trace->context_with_span($span); 43 | 44 | dynamically OpenTelemetry::Context->current = $context; 45 | 46 | my ( $error ); 47 | try { 48 | return $block->( $span, $context ); 49 | } 50 | catch ($e) { 51 | $span->record_exception($e); 52 | 53 | ($error) = split /\n/, "$e", 2; 54 | $error =~ s/ at \S+ line \d+\.$//; 55 | 56 | die $e; 57 | } 58 | finally { 59 | $span->set_status( 60 | $error ? ( SPAN_STATUS_ERROR, $error ) : SPAN_STATUS_OK 61 | ); 62 | 63 | $span->end; 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /lib/OpenTelemetry/X.pod: -------------------------------------------------------------------------------- 1 | =encoding UTF-8 2 | 3 | =head1 NAME 4 | 5 | OpenTelemetry::X - An exception factory for OpenTelemetry 6 | 7 | =head1 SYNOPSIS 8 | 9 | use OpenTelemetry::X; 10 | 11 | die OpenTelemetry::X->create( $type => $message ); 12 | 13 | =head1 DESCRIPTION 14 | 15 | Because of the nature of OpenTelemetry, the vast majority of its operations 16 | will attempt to recover from unexpected circumstances that might otherwise 17 | be considered errors: one would not want an application crashing because of 18 | the monitoring framework it uses. In these cases, OpenTelemetry prefers to 19 | log that these events took place, and carry on. 20 | 21 | Some errors, however, are the result of incorrectly using the framework's 22 | API, or are otherwise unrecoverable. This package is there to make it easier 23 | to handle and identify these cases. 24 | 25 | The created exceptions overload stringification, so they should be usable 26 | in most contexts where regular errors are used elsewhere in Perl. 27 | 28 | =head1 CLASS METHODS 29 | 30 | =head2 create 31 | 32 | $exception = OpenTelemetry::X->create( $type => $message ) 33 | 34 | Takes a string identifying an exception class and a message, and returns an 35 | instance of an exception object of that type, with that message. The type will 36 | be used as a sub-namespace under 'OpenTelemetry::X' to generate the class of 37 | the exception to be constructed. 38 | 39 | Constructed exceptions will be subclasses of OpenTelemetry::X, but this method 40 | should only be called from this package. 41 | 42 | =head1 SEE ALSO 43 | 44 | =over 45 | 46 | =item L 47 | 48 | =item L 49 | 50 | =item L 51 | 52 | =back 53 | 54 | =head1 COPYRIGHT AND LICENSE 55 | 56 | This software is copyright (c) 2023 by José Joaquín Atria. 57 | 58 | This is free software; you can redistribute it and/or modify it under the same 59 | terms as the Perl 5 programming language system itself. 60 | -------------------------------------------------------------------------------- /t/OpenTelemetry/Trace/Span/Status.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use Test2::V0 -target => 'OpenTelemetry::Trace::Span::Status'; 4 | use Test2::Tools::OpenTelemetry; 5 | 6 | is CLASS->new, object { 7 | call description => ''; 8 | call code => 0; 9 | call is_unset => T; 10 | call is_ok => F; 11 | call is_error => F; 12 | }, 'Raw constructor defaults to unset'; 13 | 14 | is CLASS->unset, object { 15 | call description => ''; 16 | call code => 0; 17 | call is_unset => T; 18 | call is_ok => F; 19 | call is_error => F; 20 | }, 'Unset constructor sets to unset'; 21 | 22 | is CLASS->ok, object { 23 | call description => ''; 24 | call code => 1; 25 | call is_unset => F; 26 | call is_ok => T; 27 | call is_error => F; 28 | }, 'Ok constructor sets to ok'; 29 | 30 | is CLASS->error, object { 31 | call description => ''; 32 | call code => 2; 33 | call is_unset => F; 34 | call is_ok => F; 35 | call is_error => T; 36 | }, 'Error constructor sets to error'; 37 | 38 | is messages { 39 | is CLASS->unset( description => 'foo' ), object { 40 | call description => ''; 41 | call code => 0; 42 | }, 'Unset constructor sets to unset'; 43 | } => [ 44 | [ warning => OpenTelemetry => 'Ignoring description on a non-error span status' ], 45 | ], 'Warns when setting a description on an unset status'; 46 | 47 | is messages { 48 | is CLASS->ok( description => 'foo' ), object { 49 | call description => ''; 50 | call code => 1; 51 | }, 'Unset constructor sets to unset'; 52 | } => [ 53 | [ warning => OpenTelemetry => 'Ignoring description on a non-error span status' ], 54 | ], 'Warns when setting a description on an ok status'; 55 | 56 | no_messages { 57 | is CLASS->error( description => 'foo' ), object { 58 | call description => 'foo'; 59 | call code => 2; 60 | }, 'Unset constructor sets to unset'; 61 | }; 62 | 63 | done_testing; 64 | -------------------------------------------------------------------------------- /lib/OpenTelemetry/Propagator/Composite.pm: -------------------------------------------------------------------------------- 1 | use Object::Pad; 2 | # ABSTRACT: A composite context propagator for OpenTelemetry 3 | 4 | package OpenTelemetry::Propagator::Composite; 5 | 6 | our $VERSION = '0.034'; 7 | 8 | class OpenTelemetry::Propagator::Composite :does(OpenTelemetry::Propagator) { 9 | use List::Util qw( uniq first ); 10 | use OpenTelemetry::Propagator::TextMap; 11 | use OpenTelemetry::X; 12 | use OpenTelemetry::Common (); 13 | 14 | my $logger = OpenTelemetry::Common::internal_logger; 15 | 16 | field @injectors; 17 | field @extractors; 18 | 19 | sub BUILDARGS ( $, @args ) { 20 | my %return = ( 21 | extractors => [ grep $_->can('extract'), @args ], 22 | injectors => [ grep $_->can('inject'), @args ], 23 | ); 24 | 25 | $logger->warnf('No suitable propagators when constructing Composite propagator') 26 | if @args 27 | && ! @{ $return{extractors} // [] } 28 | && ! @{ $return{injectors} // [] }; 29 | 30 | %return; 31 | } 32 | 33 | ADJUSTPARAMS ($params) { 34 | @injectors = @{ delete $params->{injectors} // [] }; 35 | @extractors = @{ delete $params->{extractors} // [] }; 36 | } 37 | 38 | method inject ( 39 | $carrier, 40 | $context = undef, 41 | $setter = undef 42 | ) { 43 | $context //= OpenTelemetry::Context->current; 44 | $setter //= OpenTelemetry::Propagator::TextMap::SETTER; 45 | 46 | $_->inject( $carrier, $context, $setter ) for @injectors; 47 | return $self; 48 | } 49 | 50 | method extract ( 51 | $carrier, 52 | $context = undef, 53 | $getter = undef 54 | ) { 55 | $context //= OpenTelemetry::Context->current; 56 | $getter //= OpenTelemetry::Propagator::TextMap::GETTER; 57 | 58 | my $ctx = $context; 59 | $ctx = $_->extract( $carrier, $ctx, $getter ) for @extractors; 60 | return $ctx; 61 | } 62 | 63 | method keys () { 64 | uniq map $_->keys, @injectors, @extractors 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | push: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | build: 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | os: [ ubuntu-latest ] 17 | 18 | # All supported Perl versions except latest. 19 | perl: [ 20 | '5.30', '5.32', '5.34', '5.36', '5.38', 21 | '5.40', 22 | ] 23 | 24 | # Variants of the latest Perl. 25 | include: 26 | - os: macos-latest 27 | perl: latest 28 | 29 | # FIXME: Windows tests started failing around the time we 30 | # moved to lvalue setters. Why? 31 | # - os: windows-latest 32 | # perl: '5.40' 33 | 34 | # This is effectively our normal one: all features and cover. 35 | - name: ' (all)' 36 | os: ubuntu-latest 37 | perl: latest 38 | cover: true 39 | 40 | runs-on: ${{ matrix.os }} 41 | 42 | name: Perl ${{ matrix.perl }} on ${{ matrix.os }}${{ matrix.name }} 43 | 44 | steps: 45 | - uses: actions/checkout@v2 46 | 47 | - uses: shogo82148/actions-setup-perl@v1 48 | with: 49 | perl-version: ${{ matrix.perl }} 50 | 51 | - name: Show diagnostics 52 | run: | 53 | perl -v 54 | cpanm --showdeps . 55 | 56 | # FIXME: Why do we need to install M:B:T manually 57 | # if cpanm --showdeps correctly reports it as a dependency? 58 | - name: Install dependencies 59 | run: | 60 | cpanm --installdeps -n . 61 | cpanm -n Module::Build::Tiny 62 | 63 | - if: ${{ matrix.cover }} 64 | run: | 65 | cpanm -n DBI LWP::UserAgent 66 | cpanm -n Devel::Cover::Report::Coveralls 67 | 68 | - name: Build 69 | run: | 70 | perl Build.PL 71 | perl Build build 72 | 73 | - if: ${{ matrix.cover }} 74 | env: 75 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 76 | run: cover -report Coveralls -test 77 | 78 | - if: ${{ !matrix.cover }} 79 | run: perl Build test 80 | -------------------------------------------------------------------------------- /lib/OpenTelemetry/Propagator/Composite.pod: -------------------------------------------------------------------------------- 1 | =encoding UTF-8 2 | 3 | =head1 NAME 4 | 5 | OpenTelemetry::Propagator::Composite - A composite context propagator for OpenTelemetry 6 | 7 | =head1 SYNOPSIS 8 | 9 | use OpenTelemetry::Propagator::Composite; 10 | use OpenTelemetry::Propagator::Baggage; 11 | use OpenTelemetry::Propagator::TraceContext; 12 | 13 | my $propagator = OpenTelemetry::Propagator::Composite->new( 14 | OpenTelemetry::Propagator::Baggage->new, 15 | OpenTelemetry::Propagator::TraceContext->new, 16 | ); 17 | 18 | # Inject data from the context to a carrier using all injectors in order 19 | my $carrier = {}; 20 | $propagator->inject( $carrier, $context ); 21 | 22 | # Extract data from a carrier to the context using all extractors in order 23 | my $new_context = $propagator->extract( $carrier, $context ); 24 | 25 | =head1 DESCRIPTION 26 | 27 | This package defines a propagator class that interact with the context 28 | (which can be either an implicit or explicit instance of 29 | L) and inject or extract data using a composite of 30 | objects implementing the L interface. Injectors 31 | and extractors can be set separately, and will be called in order when they 32 | are used. 33 | 34 | This composite propagator itself implements the L 35 | interface. 36 | 37 | =head1 METHODS 38 | 39 | =head2 new 40 | 41 | $propagator = OpenTelemetry::Propagator::Composite->new(@propagators) 42 | 43 | Constructs a new instance of this propagator. Takes a potentially empty list 44 | of objects that implement the L interface. Calls 45 | to C and C will be delegated to these in the order they were 46 | specified. 47 | 48 | =head1 SEE ALSO 49 | 50 | =over 51 | 52 | =item L 53 | 54 | =item L 55 | 56 | =back 57 | 58 | =head1 COPYRIGHT AND LICENSE 59 | 60 | This software is copyright (c) 2023 by José Joaquín Atria. 61 | 62 | This is free software; you can redistribute it and/or modify it under the same 63 | terms as the Perl 5 programming language system itself. 64 | -------------------------------------------------------------------------------- /t/OpenTelemetry/Common.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use Test2::V0; 4 | 5 | use OpenTelemetry::Constants qw( 6 | INVALID_TRACE_ID 7 | INVALID_SPAN_ID 8 | ); 9 | 10 | use OpenTelemetry::Common qw( 11 | config 12 | maybe_timeout 13 | timeout_timestamp 14 | generate_span_id 15 | generate_trace_id 16 | ); 17 | 18 | subtest Timeout => sub { 19 | is my $start = timeout_timestamp, T, 'Can get a monotonic timestamp'; 20 | 21 | is maybe_timeout(), U, 'No timeout'; 22 | 23 | is maybe_timeout( undef, undef ), U, 'No timeout with explicit undefs'; 24 | 25 | is maybe_timeout(0), 0, 'No time remaining'; 26 | 27 | is maybe_timeout(10, $start - 3), float( 7, tolerance => 0.1 ), 28 | 'Some time remaining'; 29 | }; 30 | 31 | subtest Config => sub { 32 | local %ENV = ( 33 | OTEL_PERL_FOO => 'OTEL_PERL_FOO', 34 | OTEL_FOO => 'OTEL_FOO', 35 | OTEL_BAR => 'OTEL_BAR', 36 | OTEL_UNDEF => undef, 37 | OTEL_EMPTY => '', 38 | OTEL_ZERO => 0, 39 | OTEL_TRUE => 'TruE', 40 | OTEL_FALSE => 'fALSe', 41 | ); 42 | 43 | is config('BAR'), 'OTEL_BAR', 'Uses OTEL prefix'; 44 | is config('FOO'), 'OTEL_PERL_FOO', 'Prefers PERL versions'; 45 | is config('MISSING'), U, 'Returns undef for missing'; 46 | is config('MISSING', 'BAR', 'FOO'), 'OTEL_BAR', 'Can fallback when undefined'; 47 | is config('EMPTY', 'FOO', 'BAR'), 'OTEL_PERL_FOO', 'Falls back when empty'; 48 | is config('MISSING', 'UNDEF', 'EMPTY', 'ZERO'), 0, 'Ignores unset and empty, but not zero'; 49 | 50 | is config('TRUE'), T, 'Reads "true" case insensitively as a true value'; 51 | is config('FALSE'), F, 'Reads "false" case insensitively as a false value'; 52 | }; 53 | 54 | subtest 'Trace ID' => sub { 55 | is my $id = generate_trace_id, T, 'Can generate a new one'; 56 | is length $id, 16, 'Has the right length'; 57 | isnt $id, INVALID_TRACE_ID, 'Is valid'; 58 | }; 59 | 60 | subtest 'Span ID' => sub { 61 | is my $id = generate_span_id, T, 'Can generate a new one'; 62 | is length $id, 8, 'Has the right length'; 63 | isnt $id, INVALID_SPAN_ID, 'Is valid'; 64 | }; 65 | 66 | done_testing; 67 | -------------------------------------------------------------------------------- /lib/OpenTelemetry/Propagator/TraceContext/TraceFlags.pod: -------------------------------------------------------------------------------- 1 | =encoding UTF-8 2 | 3 | =head1 NAME 4 | 5 | OpenTelemetry::Propagator::TraceContext::TraceFlags - Represents TraceFlags in a W3C TraceContext 6 | 7 | =head1 SYNOPSIS 8 | 9 | use OpenTelemetry::Propagator::TraceContext::TraceFlags; 10 | 11 | $off = OpenTelemetry::Propagator::TraceContext::TraceFlags->new; 12 | say 'Not sampled' unless $off->sampled; 13 | 14 | $on = OpenTelemetry::Propagator::TraceContext::TraceFlags->new(1); 15 | say 'Sampled' if $on->sampled; 16 | 17 | =head1 DESCRIPTION 18 | 19 | This class can be used to represent the trace flags as defined in a 20 | L. Currently, this 21 | implements version C<00> of that standard, so the only flag that is defined 22 | is the "sampled" flag. 23 | 24 | =head1 METHODS 25 | 26 | =head2 new 27 | 28 | $traceflags = OpenTelemetry::Propagator::TraceContext::TraceFlags->new( 29 | $numeric_value // 0, 30 | ); 31 | 32 | Takes a positional numeric value to use as the starting value for the 33 | flags. The value must be a positive integer less than or equal to 255. If 34 | the value is not, a warning will be logged and the value will be discarded. 35 | 36 | If no value is given (or if the value given is undefined), the value will be 37 | set to a default of 0, meaning all flags are disabled. 38 | 39 | =head2 flags 40 | 41 | $numeric = $traceflags->flags; 42 | 43 | Returns the flags as a plan numeric scalar. 44 | 45 | =head2 sampled 46 | 47 | $bool = $traceflags->sampled; 48 | 49 | Returns true if the sampled flag is enabled, or false otherwise. 50 | 51 | =head2 to_string 52 | 53 | $string = $traceflags->to_string; 54 | 55 | Returns the flags stringified as a lowecase hexadecimal string. 56 | 57 | =head1 SEE ALSO 58 | 59 | =over 60 | 61 | =item L 62 | 63 | =item L 64 | 65 | =item L 66 | 67 | =back 68 | 69 | =head1 COPYRIGHT AND LICENSE 70 | 71 | This software is copyright (c) 2023 by José Joaquín Atria. 72 | 73 | This is free software; you can redistribute it and/or modify it under the same 74 | terms as the Perl 5 programming language system itself. 75 | -------------------------------------------------------------------------------- /lib/OpenTelemetry/Processor.pod: -------------------------------------------------------------------------------- 1 | =encoding UTF-8 2 | 3 | =head1 NAME 4 | 5 | OpenTelemetry::Processor - An abstract interface for OpenTelemetry processors 6 | 7 | =head1 SYNOPSIS 8 | 9 | use Object::Pad; 10 | use Future::AsyncAwait; 11 | 12 | class My::Processor :does(OpenTelemetry::Processor) { 13 | method process { ... } 14 | async method shutdown { ... } 15 | async method force_flush { ... } 16 | 17 | # Any additional methods in your processor 18 | ... 19 | } 20 | 21 | my $processor = My::Processor->new; 22 | 23 | =head1 DESCRIPTION 24 | 25 | This package provides an L role that defines the interface 26 | that OpenTelemetry processor classes should implement. Processors are 27 | objects that represent the start of a pipeline that starts with a provider 28 | and will, in most cases, end with a class implementing the 29 | L role. 30 | 31 | =head1 METHODS 32 | 33 | Although there is unfortunately no way to currently enforce it, this document 34 | describes the way the methods of a class implementing this role are expected 35 | to behave. 36 | 37 | =head2 process 38 | 39 | $processor->process(@items) 40 | 41 | Takes a list of elements to process, and calls 42 | L<"export"|OpenTelemetry::Exporter/export> on the configured exporter on those 43 | elements. Returns nothing. 44 | 45 | =head2 shutdown 46 | 47 | $result = await $processor->shutdown( ... ) 48 | 49 | Calls L<"shutdown"|OpenTelemetry::Exporter/shutdown> on the configured 50 | exporter and returns a L that will hold the result of that operation. 51 | 52 | =head2 force_flush 53 | 54 | $result = await $processor->force_flush( ... ) 55 | 56 | Calls L<"force_flush"|OpenTelemetry::Exporter/force_flush> on the configured 57 | exporter and returns a L that will hold the result of that operation. 58 | 59 | =head1 SEE ALSO 60 | 61 | =over 62 | 63 | =item L 64 | 65 | =item L 66 | 67 | =item L 68 | 69 | =back 70 | 71 | =head1 COPYRIGHT AND LICENSE 72 | 73 | This software is copyright (c) 2024 by José Joaquín Atria. 74 | 75 | This is free software; you can redistribute it and/or modify it under the same 76 | terms as the Perl 5 programming language system itself. 77 | -------------------------------------------------------------------------------- /lib/OpenTelemetry/Baggage.pm: -------------------------------------------------------------------------------- 1 | use Object::Pad ':experimental(init_expr)'; 2 | # ABSTRACT: Records and propagates baggage in a distributed trace 3 | 4 | use experimental qw( signatures lexical_subs ); 5 | use OpenTelemetry::Context; 6 | 7 | my $BAGGAGE_KEY = OpenTelemetry::Context->key('baggage'); 8 | 9 | package 10 | OpenTelemetry::Baggage::Entry; 11 | 12 | our $VERSION = '0.034'; 13 | 14 | class OpenTelemetry::Baggage::Entry { 15 | field $value :param :reader; 16 | field $meta :param :reader = {}; 17 | } 18 | 19 | package 20 | OpenTelemetry::Baggage::Builder; 21 | 22 | our $VERSION = '0.024'; 23 | 24 | class OpenTelemetry::Baggage::Builder { 25 | field %data; 26 | 27 | method set ( $name, $value, $meta = undef ) { 28 | $data{$name} = OpenTelemetry::Baggage::Entry->new( 29 | value => $value, 30 | meta => $meta, 31 | ); 32 | $self; 33 | } 34 | 35 | method build ( $context = undef ) { 36 | $context //= OpenTelemetry::Context->current; 37 | $context->set( $BAGGAGE_KEY => { %data } ); 38 | } 39 | } 40 | 41 | package OpenTelemetry::Baggage; 42 | 43 | our $VERSION = '0.024'; 44 | 45 | my sub from_context ( $context = undef ) { 46 | ( $context // OpenTelemetry::Context->current )->get($BAGGAGE_KEY) // {} 47 | } 48 | 49 | sub set ( $, $name, $value, $meta = undef, $context = undef ) { 50 | $context //= OpenTelemetry::Context->current; 51 | 52 | my %new = %{ from_context $context }; 53 | $new{$name} = OpenTelemetry::Baggage::Entry->new( 54 | value => $value, 55 | meta => $meta, 56 | ); 57 | 58 | $context->set( $BAGGAGE_KEY => \%new ); 59 | } 60 | 61 | sub get ( $, $name, $context = undef ) { 62 | from_context($context)->{$name} 63 | } 64 | 65 | sub all ( $, $context = undef ) { 66 | %{ from_context($context) } 67 | } 68 | 69 | sub delete ( $, $name, $context = undef ) { 70 | $context //= OpenTelemetry::Context->current; 71 | 72 | my %new = %{ from_context $context }; 73 | return $context unless exists $new{$name}; 74 | 75 | delete $new{$name}; 76 | $context->set( $BAGGAGE_KEY => \%new ); 77 | } 78 | 79 | sub clear ( $, $context = undef ) { 80 | $context //= OpenTelemetry::Context->current; 81 | $context->delete($BAGGAGE_KEY); 82 | } 83 | 84 | sub builder ( $ ) { 85 | OpenTelemetry::Baggage::Builder->new; 86 | } 87 | 88 | 1; 89 | -------------------------------------------------------------------------------- /lib/OpenTelemetry/Propagator/TraceContext.pod: -------------------------------------------------------------------------------- 1 | =encoding UTF-8 2 | 3 | =head1 NAME 4 | 5 | OpenTelemetry::Propagator::TraceContext - Propagate context using the W3C TraceContext format 6 | 7 | =head1 SYNOPSIS 8 | 9 | use OpenTelemetry::Trace; 10 | use OpenTelemetry::Propagator::TraceContext; 11 | 12 | my $propagator = OpenTelemetry::Propagator::TraceContext; 13 | 14 | # Inject TraceContext data from the context to a carrier 15 | my $carrier = {}; 16 | $propagator->inject( $carrier, $context ); 17 | 18 | # Extract TraceContext data from a carrier to the context 19 | my $new_context = $propagator->extract( $carrier, $context ); 20 | 21 | # The TraceContext data will be in the span in the context 22 | my $span = OpenTelemetry::Trace->span_from_context($new_context); 23 | 24 | 25 | =head1 DESCRIPTION 26 | 27 | This package defines a propagator class that can interact with the context 28 | (which can be either an implicit or explicit instance of 29 | L) and inject or extract data using the 30 | L. 31 | 32 | It implements the propagator interface defined in 33 | L. 34 | 35 | =head1 METHODS 36 | 37 | =head2 inject 38 | 39 | $propagator = $propagator->inject( 40 | $carrier, 41 | $context // OpenTelemetry::Context->current, 42 | $setter // OpenTelemetry::Propagator::TextMap::SETTER, 43 | ) 44 | 45 | =head2 extract 46 | 47 | $new_context = $propagator->extract( 48 | $carrier, 49 | $context // OpenTelemetry::Context->current, 50 | $getter // OpenTelemetry::Propagator::TextMap::GETTER, 51 | ) 52 | 53 | =head2 keys 54 | 55 | @keys = $propagator->keys 56 | 57 | =head1 SEE ALSO 58 | 59 | =over 60 | 61 | =item L 62 | 63 | =item L 64 | 65 | =item L 66 | 67 | =item L 68 | 69 | A different distribution written to deal with W3C's tracecontext data, 70 | earlier than this distribution or its related L 71 | 72 | =back 73 | 74 | =head1 COPYRIGHT AND LICENSE 75 | 76 | This software is copyright (c) 2023 by José Joaquín Atria. 77 | 78 | This is free software; you can redistribute it and/or modify it under the same 79 | terms as the Perl 5 programming language system itself. 80 | -------------------------------------------------------------------------------- /lib/OpenTelemetry/Propagator/TextMap.pod: -------------------------------------------------------------------------------- 1 | =encoding UTF-8 2 | 3 | =head1 NAME 4 | 5 | OpenTelemetry::Propagator::TextMap - A context propagator for OpenTelemetry using string key / value pairs 6 | 7 | =head1 SYNOPSIS 8 | 9 | use OpenTelemetry::Propagator::TextMap; 10 | 11 | my $propagator = OpenTelemetry::Propagator::TextMap->new; 12 | 13 | # Does nothing :( 14 | my $carrier = {}; 15 | $propagator->inject( $carrier, $context ); 16 | 17 | # Does nothing :( 18 | my $new_context = $propagator->extract( $carrier, $context ); 19 | 20 | =head1 DESCRIPTION 21 | 22 | This package defines a no-op propagator class that implements the 23 | L interface, with the assumption that the carrier 24 | can store key / value pairs as strings (in Perl parlance, that the carrier 25 | can be used as a hash reference). 26 | 27 | It also exposes a L and a L that can be used 28 | as the default value by any propagator that shares this assumption. 29 | 30 | =head1 METHODS 31 | 32 | =head2 new 33 | 34 | $propagator = OpenTelemetry::Propagator::TextMap->new 35 | 36 | Constructs a new instance of this propagator. This propagator will do nothing. 37 | 38 | =head2 GETTER 39 | 40 | $getter = OpenTelemetry::Propagator::TextMap->GETTER; 41 | $value = $carrier->$getter($key); 42 | 43 | Returns a subroutine reference that takes a carrier data structure and a 44 | string key, and returns whatever value the carrier stores under that key. 45 | If no value is stored under that key, this getter returns undefined. 46 | 47 | This getter assumes the carrier is (or can be used as) a hash reference. 48 | 49 | =head2 SETTER 50 | 51 | $setter = OpenTelemetry::Propagator::TextMap->SETTER; 52 | $carrier->$setter( $key => $value ); 53 | 54 | Returns a subroutine reference that takes a carrier data structure and a 55 | string key / value pair, and stores the value under the key in the carrier. 56 | It returns nothing. 57 | 58 | This getter assumes the carrier is (or can be used as) a hash reference. 59 | 60 | =head1 SEE ALSO 61 | 62 | =over 63 | 64 | =item L 65 | 66 | =item L 67 | 68 | =back 69 | 70 | =head1 COPYRIGHT AND LICENSE 71 | 72 | This software is copyright (c) 2023 by José Joaquín Atria. 73 | 74 | This is free software; you can redistribute it and/or modify it under the same 75 | terms as the Perl 5 programming language system itself. 76 | -------------------------------------------------------------------------------- /t/OpenTelemetry/Propagator/TraceContext/TraceParent.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use Test2::V0 -target => 'OpenTelemetry::Propagator::TraceContext::TraceParent'; 4 | 5 | use OpenTelemetry::Trace::SpanContext; 6 | 7 | subtest Parsing => sub { 8 | my $tp = CLASS->from_string('00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01'); 9 | 10 | is $tp, object { 11 | call version => 0; 12 | call trace_id => pack 'H*', '4bf92f3577b34da6a3ce929d0e0e4736'; 13 | call span_id => pack 'H*', '00f067aa0ba902b7'; 14 | call trace_flags => object { 15 | call flags => 1; 16 | call sampled => T; 17 | }; 18 | }, 'Can parse TraceParent string'; 19 | 20 | is $tp->to_string, '00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01', 21 | 'Can generate TraceParent string'; 22 | 23 | is CLASS->from_string('01-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01-'), object { 24 | call version => 1; 25 | }, 'Supports newer versions if backwards compatible'; 26 | 27 | is CLASS->from_span_context( 28 | OpenTelemetry::Trace::SpanContext->new( 29 | span_id => pack( 'H*', 'f0f1f2f3f4f5f6f7' ), 30 | trace_id => pack( 'H*', '000102030405060708090a0b0c0d0e0f' ), 31 | ) 32 | )->to_string, '00-000102030405060708090a0b0c0d0e0f-f0f1f2f3f4f5f6f7-00', 'Parse from SpanContext'; 33 | 34 | like dies { CLASS->from_string('00-deadbeef') }, 35 | qr/^Could not parse TraceParent from string: '00-deadbeef'/, 36 | 'Dies when unable to parse'; 37 | 38 | like dies { CLASS->from_string('00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01-extrastuff') }, 39 | qr/^Malformed TraceParent string had trailing data after trace-flags:/, 40 | 'Dies when parsing invalid trace ID'; 41 | 42 | like dies { CLASS->from_string('00-00000000000000000000000000000000-00f067aa0ba902b7-01') }, 43 | qr/^Invalid trace ID \(00000000000000000000000000000000\) when parsing string:/, 44 | 'Dies when parsing invalid trace ID'; 45 | 46 | like dies { CLASS->from_string('00-4bf92f3577b34da6a3ce929d0e0e4736-0000000000000000-01') }, 47 | qr/^Invalid span ID \(0000000000000000\) when parsing string:/, 48 | 'Dies when parsing invalid span ID'; 49 | 50 | like dies { CLASS->from_string('01-DEADBEEFE') }, 51 | qr/^Unsupported TraceParent version \(01\) when parsing string:/, 52 | 'Dies when parsing unsupported versions that are not backwards compatible'; 53 | }; 54 | 55 | done_testing; 56 | -------------------------------------------------------------------------------- /examples/instrumentations/http-tiny: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use strict; 4 | use warnings; 5 | use feature 'say'; 6 | 7 | # This example shows the minimum amount of code needed to generate 8 | # telemetry data through an instrumentation, in this case using 9 | # HTTP::Tiny 10 | 11 | # While loading the instrumentation is all that is needed to instrument 12 | # the client code, in order to see it work we need two more things: 13 | # 14 | # 1. Some configured set of propagators, so that we have something 15 | # we can propagate data with; and 16 | # 17 | # 2. A valid trace span in the current context, so that we have 18 | # something to propagate. 19 | 20 | # The easiest way to create and install a valid trace span in the 21 | # current context is with the trace provider's 'in_span' helper 22 | # method. For that we need a way to access the tracer provider. 23 | # We can use OpenTelemetry->tracer_provider, or use this 24 | # convenience function that we can import. 25 | use OpenTelemetry qw( otel_tracer_provider ); 26 | 27 | # The easiest way to set up a set of propagators, on the other hand, 28 | # is by loading the SDK, which will read the configuration from the 29 | # environment. By default, this will set up a TraceContext and a 30 | # Baggage propagator, but we won't see the baggage in the request 31 | # headers since we are not going to add any baggage to the context. 32 | use OpenTelemetry::SDK; 33 | 34 | # We load the the instrumentation for HTTP::Tiny, which will 35 | # automatically import that module for us. This automatic loading only 36 | # happens if we request the instrumentation by name. 37 | use OpenTelemetry::Integration 'HTTP::Tiny'; 38 | 39 | # By default we will send a request to an echo server, so we can examine 40 | # the headers we send. You can override this by passing a URL as an 41 | # argument when executing this script. 42 | my $url = shift // 'https://httpbin.org/anything'; 43 | 44 | # In most realistic scenarios, telemetry data is processed asynchronously 45 | # via a batch processor or something similar. With a short-lived program 46 | # like this, we should make sure to wait until the processor flushes its 47 | # internal queue before we exit. 48 | END { otel_tracer_provider->force_flush->get } 49 | 50 | # We get a tracer from the tracer provider and use its `in_span` helper 51 | # to execute some code in a context with a valid span. The code reference 52 | # we pass will take the created span and the context that contains it 53 | # as arguments, but we don't need them in this case. 54 | otel_tracer_provider->tracer->in_span( 55 | 'some-span' => sub { 56 | my $res = HTTP::Tiny->new->get($url); 57 | say $res->{content}; 58 | }, 59 | ); 60 | -------------------------------------------------------------------------------- /examples/instrumentations/lwp: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use strict; 4 | use warnings; 5 | use feature 'say'; 6 | 7 | # This example shows the minimum amount of code needed to generate 8 | # telemetry data through an instrumentation, in this case using 9 | # LWP::UserAgent. 10 | 11 | # While loading the instrumentation is all that is needed to instrument 12 | # the client code, in order to see it work we need two more things: 13 | # 14 | # 1. Some configured set of propagators, so that we have something 15 | # we can propagate data with; and 16 | # 17 | # 2. A valid trace span in the current context, so that we have 18 | # something to propagate. 19 | 20 | # The easiest way to create and install a valid trace span in the 21 | # current context is with the trace provider's 'in_span' helper 22 | # method. For that we need a way to access the tracer provider. 23 | # We can use OpenTelemetry->tracer_provider, or use this 24 | # convenience function that we can import. 25 | use OpenTelemetry qw( otel_tracer_provider ); 26 | 27 | # The easiest way to set up a set of propagators, on the other hand, 28 | # is by loading the SDK, which will read the configuration from the 29 | # environment. By default, this will set up a TraceContext and a 30 | # Baggage propagator, but we won't see the baggage in the request 31 | # headers since we are not going to add any baggage to the context. 32 | use OpenTelemetry::SDK; 33 | 34 | # We load the the instrumentation for LWP::UserAgent, which will 35 | # automatically import that module for us. This automatic loading only 36 | # happens if we request the instrumentation by name. 37 | use OpenTelemetry::Integration 'LWP::UserAgent'; 38 | 39 | # By default we will send a request to an echo server, so we can examine 40 | # the headers we send. You can override this by passing a URL as an 41 | # argument when executing this script. 42 | my $url = shift // 'https://httpbin.org/anything'; 43 | 44 | # In most realistic scenarios, telemetry data is processed asynchronously 45 | # via a batch processor or something similar. With a short-lived program 46 | # like this, we should make sure to wait until the processor flushes its 47 | # internal queue before we exit. 48 | END { otel_tracer_provider->force_flush->get } 49 | 50 | # We get a tracer from the tracer provider and use its `in_span` helper 51 | # to execute some code in a context with a valid span. The code reference 52 | # we pass will take the created span and the context that contains it 53 | # as arguments, but we don't need them in this case. 54 | otel_tracer_provider->tracer->in_span( 55 | 'some-span' => sub { 56 | my $res = LWP::UserAgent->new->get($url); 57 | say $res->content; 58 | }, 59 | ); 60 | -------------------------------------------------------------------------------- /lib/OpenTelemetry/Trace/Span/Status.pod: -------------------------------------------------------------------------------- 1 | =encoding UTF-8 2 | 3 | =head1 NAME 4 | 5 | OpenTelemetry::Trace::Span::Status - The status of an OpenTelemetry span 6 | 7 | =head1 SYNOPSIS 8 | 9 | use OpenTelemetry::Trace::Span::Status; 10 | 11 | my $ok = OpenTelemetry::Trace::Span::Status->ok; 12 | my $unset = OpenTelemetry::Trace::Span::Status->unset; 13 | my $error = OpenTelemetry::Trace::Span::Status->error( 14 | description => 'Something went boom', 15 | ); 16 | 17 | =head1 DESCRIPTION 18 | 19 | This module provides a class that represents the status of a 20 | L. The status is represented by an internal code, 21 | and can be either Unset, which is the default value; Ok, to represent the 22 | status of an operation that is deemed to have completed successfully; or 23 | Error, to represent an operation that did not complete successfully. Error 24 | statuses can have a description string attached to them, to further explain 25 | the source of the error, but setting a description on a non-error status will 26 | issue a warning and the set description will be ignored. 27 | 28 | Although not mandatory, libraries marking the status of a span as "error" 29 | are expected to provide a description of the reason, which should be publicly 30 | documented. 31 | 32 | =head1 METHODS 33 | 34 | =head2 ok 35 | 36 | $status = OpenTelemetry::Trace::Span::Status->ok; 37 | 38 | Creates a new status object with its code set to Ok. Any additional parameters 39 | will be passed to the default constructor. 40 | 41 | =head2 unset 42 | 43 | $status = OpenTelemetry::Trace::Span::Status->unset; 44 | 45 | Creates a new status object with its code not set. Any additional parameters 46 | will be passed to the default constructor. 47 | 48 | =head2 error 49 | 50 | $status = OpenTelemetry::Trace::Span::Status->error( 51 | description => $description // '', 52 | ); 53 | 54 | Creates a new status object with its code set to Error. Any additional 55 | parameters will be passed to the default constructor. 56 | 57 | =head2 is_ok 58 | 59 | $bool = $status->is_ok; 60 | 61 | Returns a true value if this instance represents an Ok status. 62 | 63 | =head2 unset 64 | 65 | $bool = $status->is_unset; 66 | 67 | Returns a true value if this instance represents an Unset status. 68 | 69 | =head2 error 70 | 71 | $bool = $status->is_error; 72 | 73 | Returns a true value if this instance represents an Error status. 74 | 75 | =head1 COPYRIGHT AND LICENSE 76 | 77 | This software is copyright (c) 2023 by José Joaquín Atria. 78 | 79 | This is free software; you can redistribute it and/or modify it under the same 80 | terms as the Perl 5 programming language system itself. 81 | -------------------------------------------------------------------------------- /lib/OpenTelemetry/Trace/Link.pod: -------------------------------------------------------------------------------- 1 | =encoding UTF-8 2 | 3 | =head1 NAME 4 | 5 | OpenTelemetry::Trace::Link - A link to an OpenTelemetry span 6 | 7 | =head1 SYNOPSIS 8 | 9 | use OpenTelemetry::Trace::Link; 10 | 11 | my $link = OpenTelemetry::Trace::Link->new( 12 | context => $span_context, 13 | attributes => \%attributes, 14 | ); 15 | 16 | # Or more realistically 17 | use OpenTelemetry; 18 | 19 | my $span = OpenTelemetry->tracer_provider->tracer->create_span( 20 | ..., 21 | links => [ 22 | { 23 | context => $other_span->context, 24 | attributes => \%attributes, 25 | }, 26 | ... 27 | ], 28 | ); 29 | 30 | =head1 DESCRIPTION 31 | 32 | Spans created by a L (or one of its subclasses) 33 | may be linked to zero or more other spans that are causally related. This can 34 | be useful to represent batched operations, where a span representing the 35 | entire operation is linked to multiple spans representing each item making up 36 | the batch; or fork/join operations, where all forks are under a single span 37 | representing the aggregation of all tasks. See the 38 | L 39 | for more details on links. 40 | 41 | Note that links can only be added to a span at creation time, and only through 42 | the C parameter to L. Please 43 | refer to that module's documentation for more details. 44 | 45 | =head1 METHODS 46 | 47 | This class implements the L role. Please consult 48 | that module's documentation for details on the behaviours it provides. 49 | 50 | =head2 new 51 | 52 | my $link = OpenTelemetry::Trace::Link->new( 53 | context => $span_context, 54 | ); 55 | 56 | Create a new link to a different span. The context parameter must be set to 57 | a valid L, which will identify the span to 58 | link to. 59 | 60 | =head2 context 61 | 62 | $span_context = $link->context; 63 | 64 | Retrieves the span context of the span this instance links to. 65 | 66 | =head1 SEE ALSO 67 | 68 | =over 69 | 70 | =item L 71 | 72 | =item L 73 | 74 | =item L 75 | 76 | =back 77 | 78 | =head1 COPYRIGHT AND LICENSE 79 | 80 | This software is copyright (c) 2023 by José Joaquín Atria. 81 | 82 | This is free software; you can redistribute it and/or modify it under the same 83 | terms as the Perl 5 programming language system itself. 84 | -------------------------------------------------------------------------------- /t/Log/Any/Adapter/OpenTelemetry.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use Test2::V0; 4 | use Test2::Tools::Spec; 5 | 6 | use Object::Pad; 7 | use OpenTelemetry ':all'; 8 | use Log::Any '$log'; 9 | use Log::Any::Adapter; 10 | 11 | my @records; 12 | class Local::LoggerProvider :isa(OpenTelemetry::Logs::LoggerProvider) { 13 | use Test2::V0; 14 | method logger ( %args ) { 15 | return mock obj => add => [ 16 | emit_record => sub { 17 | shift; push @records => { @_ }; 18 | }, 19 | ]; 20 | } 21 | } 22 | 23 | otel_logger_provider = Local::LoggerProvider->new; 24 | 25 | Log::Any::Adapter->set('OpenTelemetry'); 26 | 27 | after_each Cleanup => sub { 28 | @records = (); 29 | }; 30 | 31 | describe Levels => sub { 32 | my ( $level, $first ); 33 | 34 | after_case Reset => sub { undef $first }; 35 | 36 | case Trace => sub { $level = 'trace' }; 37 | case Debug => sub { $level = 'debug' }; 38 | case Info => sub { $level = 'info' }; 39 | case Warn => sub { $level = 'warn' }; 40 | case Error => sub { $level = 'error' }; 41 | case Fatal => sub { $level = 'fatal' }; 42 | case Bad => sub { $level = 'bad'; $first = 'info' }; 43 | 44 | it Works => { flat => 1 } => sub { 45 | local $ENV{OTEL_LOG_LEVEL} = $level; 46 | 47 | for my $method ( Log::Any->logging_methods ) { 48 | my $numeric = Log::Any::Adapter::Util::numeric_level($method); 49 | $log->$method($numeric) 50 | } 51 | 52 | my $want = Log::Any::Adapter::Util::numeric_level( $first // $level ); 53 | is $records[0]->{body}, $want; 54 | } 55 | }; 56 | 57 | describe Body => sub { 58 | my ( @args, $body, $attributes ); 59 | 60 | case String => sub { 61 | @args = $body = 'string'; 62 | $attributes = DNE; 63 | }; 64 | 65 | case Hash => sub { 66 | @args = $body = { foo => 123 }; 67 | $attributes = DNE; 68 | }; 69 | 70 | case Array => sub { 71 | @args = ( body => [ foo => 122 ] ); 72 | 73 | ( $body, my $rest ) = @args; 74 | $attributes = { payload => $rest }; 75 | }; 76 | 77 | case Attributes => sub { 78 | @args = ( 79 | body => ( 80 | foo => 123, 81 | bar => 234, 82 | ), 83 | ); 84 | 85 | ( $body, my @rest ) = @args; 86 | $attributes = { @rest }; 87 | }; 88 | 89 | it Works => { flat => 1 } => sub { 90 | $log->info(@args); 91 | is \@records, [ 92 | { 93 | body => $body, 94 | attributes => $attributes, 95 | severity_number => E, 96 | severity_text => 'info', 97 | timestamp => T, 98 | } 99 | ]; 100 | }; 101 | }; 102 | 103 | done_testing; 104 | -------------------------------------------------------------------------------- /lib/OpenTelemetry/Propagator/TraceContext/TraceParent.pm: -------------------------------------------------------------------------------- 1 | use Object::Pad ':experimental(init_expr)'; 2 | # ABSTRACT: Represents a TraceParent in a W3C TraceContext 3 | 4 | package OpenTelemetry::Propagator::TraceContext::TraceParent; 5 | 6 | our $VERSION = '0.034'; 7 | 8 | class OpenTelemetry::Propagator::TraceContext::TraceParent { 9 | use OpenTelemetry::X; 10 | use OpenTelemetry::Constants qw( 11 | HEX_INVALID_TRACE_ID 12 | HEX_INVALID_SPAN_ID 13 | ); 14 | use OpenTelemetry::Propagator::TraceContext::TraceFlags; 15 | 16 | field $span_id :param :reader; 17 | field $trace_flags :param :reader; 18 | field $trace_id :param :reader; 19 | field $version :param :reader = 0; 20 | 21 | method to_string () { 22 | join '-', 23 | '00', 24 | unpack( 'H*', $trace_id ), 25 | unpack( 'H*', $span_id ), 26 | $trace_flags->to_string; 27 | } 28 | 29 | sub from_span_context ( $class, $context ) { 30 | $class->new( 31 | span_id => $context->span_id, 32 | trace_flags => $context->trace_flags, 33 | trace_id => $context->trace_id, 34 | ); 35 | } 36 | 37 | sub from_string ( $class, $string ) { 38 | my $version = substr $string, 0, 2; 39 | 40 | die OpenTelemetry::X->create( 41 | 'Unsupported', 42 | "Unsupported TraceParent version ($version) when parsing string: $string" 43 | ) if !$version 44 | || $version !~ /^\d+$/a 45 | || ( $version > 0 && length $string < 55 ); 46 | 47 | my ( $trace_id, $span_id, $trace_flags ) = $string =~ / 48 | ^ [A-Za-z0-9]{2} # version 49 | - ([A-Za-z0-9]{32}) # trace ID 50 | - ([A-Za-z0-9]{16}) # span ID 51 | - ([A-Za-z0-9]{2}) # trace flags 52 | ( $ | - ) 53 | /x or die OpenTelemetry::X->create( 54 | 'Parsing', 55 | "Could not parse TraceParent from string: '$string'" 56 | ); 57 | 58 | die OpenTelemetry::X->create( 59 | 'Parsing', 60 | "Malformed TraceParent string had trailing data after trace-flags: '$string'" 61 | ) if $version == 0 && length $string > 55; 62 | 63 | die OpenTelemetry::X->create( 64 | 'Parsing', 65 | "Invalid trace ID ($trace_id) when parsing string: '$string'" 66 | ) if $trace_id eq HEX_INVALID_TRACE_ID; 67 | 68 | die OpenTelemetry::X->create( 69 | 'Parsing', 70 | "Invalid span ID ($span_id) when parsing string: '$string'" 71 | ) if $span_id eq HEX_INVALID_SPAN_ID; 72 | 73 | $class->new( 74 | version => 0+$version, 75 | trace_id => pack( 'H*', $trace_id ), 76 | span_id => pack( 'H*', $span_id ), 77 | trace_flags => OpenTelemetry::Propagator::TraceContext::TraceFlags 78 | ->new( hex $trace_flags ), 79 | ); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /t/OpenTelemetry/Trace.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use Test2::V0 -target => 'OpenTelemetry::Trace'; 4 | 5 | use OpenTelemetry::Context; 6 | use OpenTelemetry::Constants qw( INVALID_SPAN_ID INVALID_TRACE_ID ); 7 | use Syntax::Keyword::Dynamically; 8 | 9 | is CLASS->span_from_context, object { 10 | call context => object { 11 | prop isa => 'OpenTelemetry::Trace::SpanContext'; 12 | call valid => F; 13 | 14 | # This is the same as calling 'valid' above 15 | call span_id => INVALID_SPAN_ID; 16 | call trace_id => INVALID_TRACE_ID; 17 | }; 18 | }, 'Returns an invalid span when none found in context'; 19 | 20 | is my $span = CLASS->non_recording_span, object { 21 | call recording => F; 22 | }, 'Can get a non-recording span'; 23 | 24 | is CLASS->non_recording_span( $span->context ), object { 25 | call recording => F; 26 | }, 'Can get a non-recording span with a context'; 27 | 28 | like dies { CLASS->context_with_span }, 29 | qr/Too few arguments for subroutine/, 30 | 'Span is needed to create a context'; 31 | 32 | my $key = OpenTelemetry::Context->key('foo'); 33 | 34 | is my $context = CLASS->context_with_span($span), object { 35 | prop isa => 'OpenTelemetry::Context'; 36 | call [ get => $key ], U; 37 | }, 'Can create a fresh context with span'; 38 | 39 | ref_is CLASS->span_from_context($context), $span, 40 | 'Can round-trip into and out of a context'; 41 | 42 | is CLASS->context_with_span( $span, $context->set( $key => 123 ) ), object { 43 | prop isa => 'OpenTelemetry::Context'; 44 | call [ get => $key ], 123; 45 | }, 'Can create a context with a span based on an existing context'; 46 | 47 | subtest 'Trace ID' => sub { 48 | is my $id = CLASS->generate_trace_id, T, 'Can generate a new one'; 49 | is length $id, 16, 'Has the right length'; 50 | isnt $id, INVALID_TRACE_ID, 'Is valid'; 51 | }; 52 | 53 | subtest 'Span ID' => sub { 54 | is my $id = CLASS->generate_span_id, T, 'Can generate a new one'; 55 | is length $id, 8, 'Has the right length'; 56 | isnt $id, INVALID_SPAN_ID, 'Is valid'; 57 | }; 58 | 59 | subtest 'Untraced context' => sub { 60 | my $root = OpenTelemetry::Context->current; 61 | 62 | is +OpenTelemetry::Trace->is_untraced_context, F, 63 | 'Root context is not untraced'; 64 | 65 | is +OpenTelemetry::Trace->is_untraced_context($root), F, 66 | 'Explicit root context is not untraced'; 67 | 68 | is my $untraced = OpenTelemetry::Trace->untraced_context, object { 69 | prop isa => 'OpenTelemetry::Context'; 70 | }, 'Untraced returns an OpenTelemetry::Context object'; 71 | 72 | is +OpenTelemetry::Trace->untraced_context($untraced), T, 73 | 'Untraced context is explicitly detected as such'; 74 | 75 | { 76 | dynamically OpenTelemetry::Context->current = $untraced; 77 | 78 | is +OpenTelemetry::Trace->is_untraced_context, T, 79 | 'Current context is untraced'; 80 | } 81 | 82 | is +OpenTelemetry::Trace->is_untraced_context, F, 83 | 'Current context is again not untraced'; 84 | }; 85 | 86 | done_testing; 87 | -------------------------------------------------------------------------------- /lib/OpenTelemetry/Context.pm: -------------------------------------------------------------------------------- 1 | use Object::Pad ':experimental(init_expr)'; 2 | # ABSTRACT: A context class for OpenTelemetry 3 | 4 | package 5 | OpenTelemetry::Context::Key; 6 | 7 | our $VERSION = '0.034'; 8 | 9 | class OpenTelemetry::Context::Key { 10 | use UUID::URandom 'create_uuid'; 11 | 12 | field $name :param; 13 | field $string :reader; 14 | 15 | ADJUST { $string = $name . '-' . unpack( 'H*', create_uuid ) }; 16 | } 17 | 18 | package OpenTelemetry::Context; 19 | 20 | our $VERSION = '0.024'; 21 | 22 | sub key ( $, $name ) { 23 | OpenTelemetry::Context::Key->new( name => $name ); 24 | } 25 | 26 | class OpenTelemetry::Context { 27 | use Carp qw( carp croak ); 28 | use List::Util qw( pairs all ); 29 | use OpenTelemetry::X; 30 | 31 | use isa 'OpenTelemetry::Context::Key'; 32 | 33 | field $data; 34 | 35 | sub BUILDARGS ( $class, %args ) { 36 | carp 'The OpenTelemetry::Context constructor no longer takes arguments' 37 | if %args; 38 | return; 39 | } 40 | 41 | method $init ( %data ) { 42 | $data = \%data; 43 | $self; 44 | } 45 | 46 | method get ( $key ) { 47 | croak +OpenTelemetry::X->create( 48 | Invalid => 'Keys in a context object must be instances of OpenTelemetry::Context::Key', 49 | ) unless isa_OpenTelemetry_Context_Key $key; 50 | 51 | $data->{ $key->string }; 52 | } 53 | 54 | method set ( @pairs ) { 55 | my %pairs; 56 | for ( pairs @pairs ) { 57 | croak +OpenTelemetry::X->create( 58 | Invalid => 'Keys in a context object must be instances of OpenTelemetry::Context::Key', 59 | ) unless isa_OpenTelemetry_Context_Key $_->[0]; 60 | 61 | $pairs{ $_->[0]->string } = $_->[1]; 62 | } 63 | 64 | OpenTelemetry::Context->new->$init( %$data, %pairs ); 65 | } 66 | 67 | method delete ( @keys ) { 68 | my @strings; 69 | for (@keys) { 70 | croak +OpenTelemetry::X->create( 71 | Invalid => 'Keys in a context object must be instances of OpenTelemetry::Context::Key', 72 | ) unless isa_OpenTelemetry_Context_Key $_; 73 | 74 | push @strings, $_->string; 75 | } 76 | 77 | my %copy = %$data; 78 | delete @copy{@strings}; 79 | 80 | OpenTelemetry::Context->new->$init(%copy); 81 | } 82 | } 83 | 84 | # Context management 85 | { 86 | use Sentinel; 87 | 88 | use isa 'OpenTelemetry::Context'; 89 | 90 | my $current = OpenTelemetry::Context->new; 91 | sub current :lvalue { 92 | sentinel( 93 | get => sub { $current }, 94 | set => sub { 95 | croak +OpenTelemetry::X->create( 96 | Invalid => 'Current context must be an instance of OpenTelemetry::Context, received instead ' . ref $_[0], 97 | ) unless isa_OpenTelemetry_Context $_[0]; 98 | 99 | $current = $_[0]; 100 | }, 101 | ); 102 | } 103 | } 104 | 105 | 1; 106 | -------------------------------------------------------------------------------- /t/OpenTelemetry/Context.t: -------------------------------------------------------------------------------- 1 | #!/bin/env perl 2 | 3 | use Test2::V0 -target => 'OpenTelemetry::Context'; 4 | 5 | use OpenTelemetry; 6 | use Syntax::Keyword::Dynamically; 7 | 8 | my ($ctx, $key, $otr, $new); 9 | 10 | $ctx = CLASS->new; 11 | $key = $ctx->key('foo'); 12 | $otr = $ctx->key('bar'); 13 | 14 | like warning { CLASS->new( foo => 123 ) }, 15 | qr/constructor no longer takes arguments/, 16 | 'Constructor no longer takes arguments and warns about it'; 17 | 18 | is $ctx, object { prop isa => 'OpenTelemetry::Context' }, 'Constructed a context'; 19 | is $key, object { prop isa => 'OpenTelemetry::Context::Key' }, 'Constructed a key'; 20 | 21 | isnt $key->string, $ctx->key('foo')->string, 22 | 'Generating two keys with the same name returns different keys'; 23 | 24 | is $ctx->get($key), U, 'Key starts undefined'; 25 | 26 | is $new = $ctx->set( $key => 12 ), 27 | object { prop isa => 'OpenTelemetry::Context' }, 28 | 'Setting a key returns new context'; 29 | 30 | is $new->get($key), 12, 'Can read set value'; 31 | is $ctx->get($key), U, 'Original context is unaffected'; 32 | 33 | is $new = $ctx->set( $key => 234, $key => 123, $otr => 'abc' ), 34 | object { prop isa => 'OpenTelemetry::Context' }, 35 | 'Setting multiple keys returns new context'; 36 | 37 | is $new->get($key), 123, 'Later assignments override earlier ones'; 38 | is $new->get($otr), 'abc', 'All set values are set'; 39 | 40 | is $new->delete( $otr, $key ), 41 | object { 42 | call [ get => $key ] => U; 43 | call [ get => $otr ] => U; 44 | }, 45 | 'Can delete values from the context'; 46 | 47 | is $new->get($key), 123, 'Original context is unaffected'; 48 | 49 | like dies { $ctx->get('foo') }, 50 | qr/^Keys in a context object must be instances of OpenTelemetry::Context::Key/, 51 | 'Validate key on get'; 52 | 53 | like dies { $ctx->set( $key => 123, foo => 123, $otr => 'abc' ) }, 54 | qr/^Keys in a context object must be instances of OpenTelemetry::Context::Key/, 55 | 'Validate key on set'; 56 | 57 | like dies { $ctx->delete( $key, 'foo', $otr ) }, 58 | qr/^Keys in a context object must be instances of OpenTelemetry::Context::Key/, 59 | 'Validate key on delete'; 60 | 61 | subtest 'Context management' => sub { 62 | my $key = CLASS->key('x'); 63 | 64 | is CLASS->current->get($key), U, 'Key is undefined in top-level context'; 65 | 66 | { 67 | dynamically CLASS->current = CLASS->current->set( $key => 123 ); 68 | is CLASS->current->get($key), 123, 'New context masks top-level'; 69 | 70 | { 71 | dynamically CLASS->current = CLASS->current->set( $key => 234 ); 72 | is CLASS->current->get($key), 234, 'New context masks old context'; 73 | } 74 | 75 | is CLASS->current->get($key), 123, 'Restored previous context'; 76 | 77 | like dies { CLASS->current = 'garbage' }, 78 | qr/^Current context must be an instance of OpenTelemetry::Context/, 79 | 'Current context is validated on write'; 80 | 81 | is CLASS->current->get($key), 123, 'Failed modification does nothing'; 82 | } 83 | 84 | is CLASS->current->get($key), U, 'Back to top-level context'; 85 | }; 86 | 87 | done_testing; 88 | -------------------------------------------------------------------------------- /t/OpenTelemetry/Instrumentation.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use Test2::V0 -target => 'OpenTelemetry::Instrumentation'; 4 | use Test2::Tools::OpenTelemetry; 5 | 6 | use Class::Inspector; 7 | 8 | my @plugins = CLASS->plugins; 9 | my $mock = mock $CLASS => override => [ plugins => sub { 10 | return @plugins, 'OpenTelemetry::Integration::Local::Only::Legacy'; 11 | } ]; 12 | 13 | # Not using Test2::Require::Module because it loads the module when checking 14 | skip_all 'Module HTTP::Tiny is not installed' 15 | unless Class::Inspector->installed('HTTP::Tiny'); 16 | 17 | subtest 'No arguments to import' => sub { 18 | no_messages { CLASS->import }; 19 | CLASS->unimport; 20 | }; 21 | 22 | subtest 'Falsy arguments to import' => sub { 23 | no_messages { CLASS->import( '', undef ) }; 24 | CLASS->unimport; 25 | }; 26 | 27 | subtest 'Load all plugins' => sub { 28 | is messages { CLASS->import(':all') } => bag { 29 | item [ 30 | trace => 'OpenTelemetry', 31 | "Loading ${CLASS}::HTTP::Tiny", 32 | ]; 33 | item [ 34 | trace => 'OpenTelemetry', 35 | "${CLASS}::HTTP::Tiny did not install itself", 36 | ]; 37 | item [ 38 | trace => 'OpenTelemetry', 39 | "Loading ${CLASS}::DBI", 40 | ]; 41 | item [ 42 | trace => 'OpenTelemetry', 43 | "${CLASS}::DBI did not install itself", 44 | ]; 45 | etc; 46 | }, 'Did not install anything because dependencies were not loaded'; 47 | 48 | is + Class::Inspector->loaded('HTTP::Tiny'), F, 49 | 'Did not load HTTP::Tiny automatically'; 50 | 51 | is + Class::Inspector->loaded('DBI'), F, 52 | 'Did not load DBI automatically'; 53 | 54 | CLASS->unimport; 55 | }; 56 | 57 | subtest 'Load a good plugin by name' => sub { 58 | is messages { 59 | CLASS->import('HTTP::Tiny'); 60 | } => [ 61 | [ 62 | trace => 'OpenTelemetry', 63 | "Loading ${CLASS}::HTTP::Tiny", 64 | ], 65 | ]; 66 | 67 | is + Class::Inspector->loaded('HTTP::Tiny'), T, 68 | 'Loaded dependency automatically'; 69 | 70 | CLASS->unimport; 71 | }; 72 | 73 | subtest 'Load a missing plugin' => sub { 74 | is messages { 75 | CLASS->import('Fake::Does::Not::Exist'); 76 | } => [ 77 | [ 78 | warning => 'OpenTelemetry', 79 | match qr/^Unable to load OpenTelemetry instrumentation for Fake::Does::Not::Exist: Can't locate/, 80 | ], 81 | ]; 82 | 83 | CLASS->unimport; 84 | }; 85 | 86 | subtest 'For package' => sub { 87 | is CLASS->for_package('HTTP::Tiny'), 'OpenTelemetry::Instrumentation::HTTP::Tiny', 88 | 'Returns the package name of an available instrumentation library'; 89 | 90 | is CLASS->for_package('Fake::Package'), U, 91 | 'Returns undefined if there is no instrumentaiton'; 92 | 93 | is CLASS->for_package(undef), U, 94 | 'Garbage in, garbage out'; 95 | 96 | is CLASS->for_package('Local::Only::Legacy'), 'OpenTelemetry::Integration::Local::Only::Legacy', 97 | 'Falls back to legacy namespace if new is not available'; 98 | }; 99 | 100 | done_testing; 101 | -------------------------------------------------------------------------------- /t/OpenTelemetry/Propagator/Composite.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use Test2::V0 -target => 'OpenTelemetry::Propagator::Composite'; 4 | use Test2::Tools::OpenTelemetry; 5 | 6 | use OpenTelemetry::Baggage; 7 | use OpenTelemetry::Propagator::Baggage; 8 | use OpenTelemetry::Propagator::TraceContext::TraceFlags; 9 | use OpenTelemetry::Propagator::TraceContext::TraceParent; 10 | use OpenTelemetry::Propagator::TraceContext; 11 | use OpenTelemetry::Trace; 12 | use OpenTelemetry::Trace::SpanContext; 13 | 14 | my $context = do { 15 | my $parent = OpenTelemetry::Propagator::TraceContext::TraceParent->new( 16 | trace_id => pack( 'H*', '000102030405060708090a0b0c0d0e0f' ), 17 | span_id => pack( 'H*', '0001020304050607' ), 18 | trace_flags => OpenTelemetry::Propagator::TraceContext::TraceFlags->new, 19 | ); 20 | 21 | my $state = OpenTelemetry::Propagator::TraceContext::TraceState 22 | ->from_string('foo=123,bar=234'); 23 | 24 | my $span_context = OpenTelemetry::Trace::SpanContext->new( 25 | trace_id => $parent->trace_id, 26 | span_id => $parent->span_id, 27 | trace_flags => $parent->trace_flags, 28 | trace_state => $state, 29 | remote => 1, 30 | ); 31 | 32 | my $span = OpenTelemetry::Trace->non_recording_span( $span_context ); 33 | 34 | OpenTelemetry::Trace->context_with_span( 35 | $span => OpenTelemetry::Baggage->set( abc => 'ABC', 'META' ), 36 | ); 37 | }; 38 | 39 | my $carrier = {}; 40 | my $prop = CLASS->new( 41 | OpenTelemetry::Propagator::Baggage->new, 42 | OpenTelemetry::Propagator::TraceContext->new, 43 | ); 44 | 45 | ref_is $prop->inject( $carrier, $context ), $prop, 'Inject returns self'; 46 | 47 | is $carrier, { 48 | baggage => 'abc=ABC;META', 49 | traceparent => '00-000102030405060708090a0b0c0d0e0f-0001020304050607-00', 50 | tracestate => 'foo=123,bar=234', 51 | }, 'Baggage injected into carrier'; 52 | 53 | is $prop->extract($carrier), object { 54 | prop isa => 'OpenTelemetry::Context'; 55 | # Extracts Baggage 56 | prop this => validator sub { 57 | my $entry = OpenTelemetry::Baggage->get( abc => $_ ); 58 | $entry 59 | && $entry->value eq 'ABC' 60 | && $entry->meta eq 'META'; 61 | }; 62 | # Extracts TraceContext 63 | prop this => validator sub { 64 | my $sctx = OpenTelemetry::Trace->span_from_context($_)->context; 65 | $sctx 66 | && $sctx->hex_trace_id eq '000102030405060708090a0b0c0d0e0f' 67 | && $sctx->hex_span_id eq '0001020304050607' 68 | && $sctx->trace_state->to_string eq 'foo=123,bar=234' 69 | && $sctx->remote; 70 | }; 71 | }, 'Extract'; 72 | 73 | is CLASS->new, object { 74 | prop isa => $CLASS; 75 | }, 'Constructor can be called with no injectors / extractors for no-op instance'; 76 | 77 | is messages { 78 | is CLASS->new( mock ), object { 79 | prop isa => $CLASS; 80 | }, 'Constructor with unsuitable injectors / extractors still builds'; 81 | } => [ 82 | [ warning => OpenTelemetry => match qr/^No suitable propagators when/ ], 83 | ], 'Constructing with no suitable propagators warns'; 84 | 85 | is [ sort $prop->keys ] , [qw( baggage traceparent tracestate )], 86 | 'Get compound keys'; 87 | 88 | done_testing; 89 | -------------------------------------------------------------------------------- /t/OpenTelemetry/Baggage.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use Test2::V0 -target => 'OpenTelemetry::Baggage'; 4 | 5 | use OpenTelemetry::Context; 6 | 7 | subtest 'No baggage' => sub { 8 | my $k = OpenTelemetry::Context->key('foo'); 9 | my $context = OpenTelemetry::Context->current->set( $k => 123 ); 10 | 11 | is + CLASS->get('foo'), U, 12 | '->get returns undef with no baggage'; 13 | 14 | is + CLASS->get( foo => $context ), U, 15 | '->get returns undef with no baggage (explicit context)'; 16 | 17 | is + CLASS->delete('foo'), object { 18 | call [ get => $k ] => U; 19 | prop isa => 'OpenTelemetry::Context'; 20 | prop this => validator sub { ! defined CLASS->get('foo') }; 21 | }, '->delete returns context without key'; 22 | 23 | is + CLASS->delete( foo => $context ), object { 24 | call [ get => $k ] => 123; 25 | prop isa => 'OpenTelemetry::Context'; 26 | prop this => validator sub { ! defined CLASS->get('foo') }; 27 | }, '->delete returns context without key (explicit context)'; 28 | 29 | is { CLASS->all }, {}, 30 | '->all returns no pairs'; 31 | 32 | is { CLASS->all($context) }, {}, 33 | '->all returns no pairs (explicit context)'; 34 | 35 | is + CLASS->clear, object { 36 | call [ get => $k ] => U; 37 | prop isa => 'OpenTelemetry::Context'; 38 | prop this => validator sub { ! CLASS->all($_) }; 39 | }, '->clear returns no pairs'; 40 | 41 | is + CLASS->clear($context), object { 42 | call [ get => $k ] => 123; 43 | prop isa => 'OpenTelemetry::Context'; 44 | prop this => validator sub { ! CLASS->all($_) }; 45 | }, '->clear returns no pairs (explicit context)'; 46 | 47 | is + CLASS->set( foo => 'ok' ), object { 48 | call [ get => $k ] => U; 49 | prop isa => 'OpenTelemetry::Context'; 50 | prop this => validator sub { CLASS->get( foo => $_ )->value eq 'ok' }; 51 | }, '->set returns context with key'; 52 | 53 | is + CLASS->set( foo => 'ok', 'meta', $context ), object { 54 | call [ get => $k ] => 123; 55 | prop isa => 'OpenTelemetry::Context'; 56 | prop this => validator sub { CLASS->get( foo => $_ )->value eq 'ok' }; 57 | }, '->set returns context with key (explicit context)'; 58 | }; 59 | 60 | subtest 'With baggage' => sub { 61 | my $context = CLASS->builder 62 | ->set( foo => 'ok', 'meta' ) 63 | ->set( bar => 'ko' ) 64 | ->build; 65 | 66 | my $deleted = CLASS->delete( foo => $context ); 67 | my $cleared = CLASS->clear($context); 68 | 69 | is { CLASS->all($context) }, { 70 | foo => object { 71 | prop isa => 'OpenTelemetry::Baggage::Entry'; 72 | call value => 'ok'; 73 | call meta => 'meta'; 74 | }, 75 | bar => object { 76 | prop isa => 'OpenTelemetry::Baggage::Entry'; 77 | call value => 'ko'; 78 | call meta => U; 79 | }, 80 | }, 'Set multiple keys'; 81 | 82 | is { CLASS->all($deleted) }, { 83 | bar => object { 84 | prop isa => 'OpenTelemetry::Baggage::Entry'; 85 | call value => 'ko'; 86 | call meta => U; 87 | }, 88 | }, 'Deleted single entry'; 89 | 90 | is { CLASS->all($cleared) }, {}, 'No baggage after clear'; 91 | }; 92 | 93 | done_testing; 94 | -------------------------------------------------------------------------------- /lib/OpenTelemetry/Trace/Event.pod: -------------------------------------------------------------------------------- 1 | =encoding UTF-8 2 | 3 | =head1 NAME 4 | 5 | OpenTelemetry::Trace::Event - An event associated with an OpenTelemetry span 6 | 7 | =head1 SYNOPSIS 8 | 9 | use OpenTelemetry::Trace::Event; 10 | 11 | my $event = OpenTelemetry::Trace::Event->new( 12 | name => $event_name, 13 | attributes => \%attributes, 14 | ); 15 | 16 | # Or more realistically 17 | use OpenTelemetry; 18 | 19 | my $span = OpenTelemetry->tracer_provider->tracer->create_span(...); 20 | 21 | ... 22 | 23 | $span->add_event( 24 | name => 'my event', 25 | attributes => \%attributes, 26 | ); 27 | 28 | =head1 DESCRIPTION 29 | 30 | Spans created by a L (or one of its subclasses) 31 | may record zero or more events. Events can represent all sorts of matters of 32 | interest, and the most common ones have been given some standardised names 33 | and set of attributes. Please refer to the 34 | L 35 | document for details on which these are. 36 | 37 | Probably the most common event will be exceptions added through a method like 38 | L. 39 | 40 | =head1 METHODS 41 | 42 | This class implements the L role. Please consult 43 | that module's documentation for details on the behaviours it provides. 44 | 45 | =head2 new 46 | 47 | my $event = OpenTelemetry::Trace::Event->new( 48 | name => $event_name, 49 | timestamp => $timestamp // time, 50 | attributes => \%attributes, 51 | ); 52 | 53 | Create a new event. The name parameter is required and must be set to a string 54 | that will identify this event. Note that some event names have standardised 55 | meanings. Refer to the 56 | L 57 | for details on what these are and what they represent. 58 | 59 | The constructor accepts an optional timestamp argument to represent the time 60 | at which this event took place. If not provided, the timestamp will be the one 61 | at which the event was created. Note that events associated with a span may 62 | have a timestamp that took place before their span started, or after it ended. 63 | 64 | While the constructor allows you to create events, users will most likely 65 | create events on a span using L instead. 66 | 67 | =head2 name 68 | 69 | $name = $event->name; 70 | 71 | Reads the name given to this event when created. 72 | 73 | =head2 timestamp 74 | 75 | $timestamp = $event->timestamp; 76 | 77 | Reads the timestamp associated with this event. 78 | 79 | =head1 SEE ALSO 80 | 81 | =over 82 | 83 | =item L 84 | 85 | =item L 86 | 87 | =item L 88 | 89 | =back 90 | 91 | =head1 COPYRIGHT AND LICENSE 92 | 93 | This software is copyright (c) 2023 by José Joaquín Atria. 94 | 95 | This is free software; you can redistribute it and/or modify it under the same 96 | terms as the Perl 5 programming language system itself. 97 | -------------------------------------------------------------------------------- /lib/OpenTelemetry/Common.pm: -------------------------------------------------------------------------------- 1 | package 2 | OpenTelemetry::Internal::Logger; 3 | 4 | use Carp::Clan '^(?:OpenTelemetry\b|Log::Any::Proxy$)'; 5 | use parent 'Log::Any::Adapter::Stderr'; 6 | 7 | foreach my $method ( Log::Any::Adapter::Util::logging_methods() ) { 8 | no strict 'refs'; 9 | my $method_level = Log::Any::Adapter::Util::numeric_level($method); 10 | *{$method} = sub { 11 | my ( $self, $text ) = @_; 12 | return if $method_level > $self->{log_level}; 13 | 14 | # NOTE: We are using Carp::Clan so we carp from non-OTel 15 | # classes, but Carp::Clan for some reason likes to prepend 16 | # sub names, which we don't want 17 | local $SIG{__WARN__} = sub { warn shift =~ s/^.*?: //r }; 18 | 19 | carp "$text"; 20 | }; 21 | } 22 | 23 | package 24 | OpenTelemetry::Common; 25 | 26 | # ABSTRACT: Utility package with shared functions for OpenTelemetry 27 | 28 | our $VERSION = '0.034'; 29 | 30 | use strict; 31 | use warnings; 32 | use experimental 'signatures'; 33 | 34 | use Bytes::Random::Secure (); 35 | use List::Util qw( any first ); 36 | use OpenTelemetry::Constants qw( INVALID_TRACE_ID INVALID_SPAN_ID ); 37 | use Ref::Util qw( is_arrayref is_hashref ); 38 | use Time::HiRes qw( clock_gettime CLOCK_MONOTONIC ); 39 | 40 | use parent 'Exporter'; 41 | 42 | our @EXPORT_OK = qw( 43 | config 44 | generate_span_id 45 | generate_trace_id 46 | maybe_timeout 47 | timeout_timestamp 48 | internal_logger 49 | ); 50 | 51 | use Log::Any; 52 | my $logger = Log::Any->get_logger( 53 | category => 'OpenTelemetry', 54 | $ENV{LOG_ANY_DEFAULT_ADAPTER} ? () : ( 55 | default_adapter => [ 56 | '+OpenTelemetry::Internal::Logger', 57 | log_level => $ENV{OTEL_PERL_INTERNAL_LOG_LEVEL} // 'warn', 58 | ], 59 | ), 60 | ); 61 | 62 | # Undocumented because this is really only for internal use 63 | sub internal_logger { $logger } 64 | 65 | sub timeout_timestamp :prototype() { 66 | clock_gettime CLOCK_MONOTONIC; 67 | } 68 | 69 | sub maybe_timeout ( $timeout = undef, $start = undef ) { 70 | return unless defined $timeout; 71 | 72 | $timeout -= ( timeout_timestamp - ( $start // 0 ) ); 73 | 74 | $timeout > 0 ? $timeout : 0; 75 | } 76 | 77 | # As per https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/configuration/sdk-environment-variables.md 78 | sub config ( @keys ) { 79 | return unless @keys; 80 | 81 | my ($value) = first { defined && length } @ENV{ 82 | map { 'OTEL_PERL_' . $_, 'OTEL_' . $_ } @keys 83 | }; 84 | 85 | return $value unless defined $value; 86 | 87 | $value =~ /^true$/i ? 1 : $value =~ /^false$/i ? 0 : $value; 88 | } 89 | 90 | # Trace functions 91 | sub generate_trace_id { 92 | while (1) { 93 | my $id = Bytes::Random::Secure::random_bytes 16; 94 | return $id unless $id eq INVALID_TRACE_ID; 95 | } 96 | } 97 | 98 | sub generate_span_id { 99 | while (1) { 100 | my $id = Bytes::Random::Secure::random_bytes 8; 101 | return $id unless $id eq INVALID_SPAN_ID; 102 | } 103 | } 104 | 105 | delete $OpenTelemetry::Common::{$_} for qw( 106 | CLOCK_MONOTONIC 107 | INVALID_SPAN_ID 108 | INVALID_TRACE_ID 109 | any 110 | clock_gettime 111 | first 112 | is_arrayref 113 | is_hashref 114 | ); 115 | 116 | 1; 117 | -------------------------------------------------------------------------------- /lib/OpenTelemetry/Logs/LoggerProvider.pod: -------------------------------------------------------------------------------- 1 | =encoding UTF-8 2 | 3 | =head1 NAME 4 | 5 | OpenTelemetry::Logs::LoggerProvider - Provides access to OpenTelemetry Loggers 6 | 7 | =head1 SYNOPSIS 8 | 9 | use OpenTelemetry; 10 | 11 | # Read the globally set provider 12 | my $provider = OpenTelemetry->logger_provider; 13 | my $logger = $provider->logger; 14 | $logger->emit_record( body => 'Reticulating splines' ); 15 | 16 | # Set a global logger provider 17 | OpenTelemetry->logger_provider = $another_provider; 18 | 19 | =head1 DESCRIPTION 20 | 21 | As implied by its name, the logger provider is responsible for providing 22 | access to a usable instance of L, which can in 23 | turn be used to emit log records. 24 | 25 | The provider implemented in this package returns an instance of 26 | L which is cached internally. This behaviour 27 | can be modified by inheriting from this class and providing a different 28 | implementation of the L method described below. See 29 | L for a way to set this modified version as a 30 | globally available logger provider. 31 | 32 | =head1 METHODS 33 | 34 | =head2 new 35 | 36 | $provider = OpenTelemetry::Logs::LoggerProvider->new 37 | 38 | Creates a new instance of the logger provider. 39 | 40 | =head2 logger 41 | 42 | $logger = $logger_provider->logger( %args ) 43 | 44 | Takes a set of named parameters, and returns a logger that can be used to 45 | emit log records via L. Accepted 46 | parameters are: 47 | 48 | =over 49 | 50 | =item C 51 | 52 | A name that uniquely identifies an 53 | L. This can 54 | be the instrumentation library, a package name, etc. This value I be 55 | set to a non-empty string. 56 | 57 | =item C 58 | 59 | Specifies the version of the 60 | L, if one is 61 | available. 62 | 63 | =item C 64 | 65 | A hash reference with a set of attributes for this 66 | L. 67 | 68 | =item C 69 | 70 | The schema URL to be recorded in the emitted data. 71 | 72 | =back 73 | 74 | The code implemented in this package ignores all arguments and returns a 75 | L, but subclasses (most notably 76 | L) are free to modify this. 77 | 78 | Callers are free to cache this logger, which logger providers must ensure 79 | can continue to work. In the event that the configuration of the logger 80 | provider has changed, it is the responsibility of the provider to 81 | propagate these changes to existing loggers, or to ensure that existing 82 | loggers remain usable. 83 | 84 | That said, callers should be aware that logger providers I change, 85 | even in limited scopes, and while the logger provider is responsible for 86 | looking after the loggers it has generated, they are not required (and may 87 | not be capable) to alter the functioning of loggers that have been created 88 | by other providers. 89 | 90 | If creating the logger is expensive, then it's the logger provider's 91 | responsibility to cache it. 92 | 93 | =head1 COPYRIGHT AND LICENSE 94 | 95 | This software is copyright (c) 2024 by José Joaquín Atria. 96 | 97 | This is free software; you can redistribute it and/or modify it under the same 98 | terms as the Perl 5 programming language system itself. 99 | -------------------------------------------------------------------------------- /lib/OpenTelemetry/Attributes.pod: -------------------------------------------------------------------------------- 1 | =encoding UTF-8 2 | 3 | =head1 NAME 4 | 5 | OpenTelemetry::Attributes - A common role for OpenTelemetry classes with attributes 6 | 7 | =head1 SYNOPSIS 8 | 9 | class My::Class :does(OpenTelemetry::Attributes) { } 10 | 11 | my $class = My::Class->new( 12 | attributes => \%attributes, 13 | ); 14 | 15 | my $read_only = $class->attributes; 16 | 17 | say $class->dropped_attributes; 18 | 19 | =head1 DESCRIPTION 20 | 21 | A number of OpenTelemetry classes allow for arbitrary attributes to be stored 22 | on them. Since the rules for these attributes are shared by all of them, this 23 | module provides a role that can be consumed by any class that should have 24 | attributes, and makes it possible to have a consistent behaviour in all of 25 | them. 26 | 27 | See the 28 | L 29 | for more details on these behaviours. 30 | 31 | =head2 Allowed values 32 | 33 | The values stored in an OpenTelemetry attribute hash can be any defined scalar 34 | that is not a reference. The only exceptions to this rule are array references, 35 | which are allowed as values as long as they do not contain any values that are 36 | themselves undefined or references (of any kind). 37 | 38 | =head2 Limits 39 | 40 | This role can optionally be configured to limit the number of attributes 41 | it will store, and the length of the stored values. If configured in this way, 42 | information about how many attributes were dropped will be made available 43 | via the L method described below. 44 | 45 | =head1 METHODS 46 | 47 | =head2 new 48 | 49 | $instance = Class::Consuming::Role->new( 50 | attributes => \%attributes // {}, 51 | attribute_count_limit => $count // undef, 52 | attribute_length_limit => $length // undef, 53 | ); 54 | 55 | Creates a new instance of the class that consumes this role. A hash reference 56 | passed as the value for the C parameter will be used as the 57 | initial set of attributes. 58 | 59 | The C and C parameters passed 60 | to the constructor can optionally be used to limit the number of fields the 61 | attribute store will hold, and the length of the stored values. If not set, 62 | the store will have no limit. 63 | 64 | If the length limit is set, fields set to plain scalar values will be 65 | truncated at that limit when set. In the case of values that are array 66 | references, the length limit will apply to each individual value. 67 | 68 | =head2 attributes 69 | 70 | $hash = $class->attributes; 71 | 72 | Returns a hash reference with a copy of the stored attributes. Because this 73 | is a copy, the returned hash reference is read-only. 74 | 75 | =head2 dropped_attributes 76 | 77 | $count = $class->dropped_attributes; 78 | 79 | Return the number of attributes that were dropped if attribute count limits 80 | have been configured (see L, described above). 81 | 82 | =head1 SEE ALSO 83 | 84 | =over 85 | 86 | =item L 87 | 88 | =back 89 | 90 | =head1 COPYRIGHT AND LICENSE 91 | 92 | This software is copyright (c) 2023 by José Joaquín Atria. 93 | 94 | This is free software; you can redistribute it and/or modify it under the same 95 | terms as the Perl 5 programming language system itself. 96 | -------------------------------------------------------------------------------- /lib/OpenTelemetry/Propagator/TraceContext/TraceState.pod: -------------------------------------------------------------------------------- 1 | =encoding UTF-8 2 | 3 | =head1 NAME 4 | 5 | OpenTelemetry::Propagator::TraceContext::TraceState - Represents TraceState in a W3C TraceContext 6 | 7 | =head1 SYNOPSIS 8 | 9 | use OpenTelemetry::Propagator::TraceContext::TraceState; 10 | 11 | $state = OpenTelemetry::Propagator::TraceContext::TraceState 12 | ->from_string('rojo=00f067aa0ba902b7,congo=t61rcWkgMzE'); 13 | 14 | say $state->get('rojo'); # 00f067aa0ba902b7 15 | say $state->get('missing'); # undef 16 | 17 | # Object is read-only: write operations generate clones 18 | $new = $state 19 | ->delete('congo') 20 | ->set( blue => 'deadbeef' ); 21 | 22 | say $state->to_string; # rojo=00f067aa0ba902b7,congo=t61rcWkgMzE 23 | say $new->to_string; # rojo=00f067aa0ba902b7,blue=deadbeef 24 | 25 | =head1 DESCRIPTION 26 | 27 | This class can be used to parse, manipulate, and generate the tracestate 28 | header as defined in a 29 | L. Currently, this 30 | implements version C<00> of that standard. 31 | 32 | Note that, as defined in that standard, this header is only to be used to 33 | store properties that are defined by a tracing system. If you are looking 34 | to propagate other application-level properties 35 | L might be a better fit. 36 | 37 | =head1 METHODS 38 | 39 | =head2 new 40 | 41 | $tracestate = OpenTelemetry::Propagator::TraceContext::TraceState->new; 42 | 43 | Creates an empty OpenTelemetry::Propagator::TraceContext::TraceState object. 44 | To create an instance with initial key/value pairs, you should use 45 | L. 46 | 47 | =head2 from_string 48 | 49 | $tracestate = OpenTelemetry::Propagator::TraceContext::TraceState 50 | ->from_string($tracestate_header); 51 | 52 | Takes the value of a tracestate header and returns a 53 | OpenTelemetry::Propagator::TraceContext::TraceState instance with the 54 | key/value pairs it contained, if any. 55 | 56 | =head2 to_string 57 | 58 | $header = $tracestate->to_string; 59 | 60 | Returns a string representation of the calling object, suitable to be used 61 | as the value of a tracestate header. 62 | 63 | =head2 get 64 | 65 | $value = $tracestate->get($key); 66 | 67 | Takes the key of a field as a string, and returns the value stored in the 68 | calling object under that key. If the key was never set, this will return 69 | an undefined value. 70 | 71 | =head2 set 72 | 73 | $new = $tracestate->set( $key => $value ); 74 | 75 | Takes a key/value pair and returns a clone of the calling object with the 76 | specified key set to the specified value. If the key already existed, its 77 | value will be over-written. 78 | 79 | =head2 delete 80 | 81 | $new = $tracestate->delete($key); 82 | 83 | Takes the key of a field as a string, returns a clone of the calling object 84 | without the specified key. If the key did not exist, already existed, its 85 | value will be over-written. 86 | 87 | =head1 SEE ALSO 88 | 89 | =over 90 | 91 | =item L 92 | 93 | =item L 94 | 95 | =back 96 | 97 | =head1 COPYRIGHT AND LICENSE 98 | 99 | This software is copyright (c) 2023 by José Joaquín Atria. 100 | 101 | This is free software; you can redistribute it and/or modify it under the same 102 | terms as the Perl 5 programming language system itself. 103 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OpenTelemetry for Perl 2 | 3 | [![Coverage Status]][coveralls] 4 | 5 | This is part of an ongoing attempt at implementing the OpenTelemetry 6 | standard in Perl. The distribution in this repository implements the 7 | abstract OpenTelemetry API, with [an SDK implementation] being worked on 8 | separately. 9 | 10 | ## What is OpenTelemetry? 11 | 12 | OpenTelemetry is an open source observability framework, providing a 13 | general-purpose API, SDK, and related tools required for the instrumentation 14 | of cloud-native software, frameworks, and libraries. 15 | 16 | OpenTelemetry provides a single set of APIs, libraries, agents, and collector 17 | services to capture distributed traces and metrics from your application. You 18 | can analyze them using Prometheus, Jaeger, and other observability tools. 19 | 20 | ## How does this distribution fit in? 21 | 22 | This distribution defines the core OpenTelemetry interfaces in the form of 23 | abstract classes and no-op implementations. That is, it defines interfaces and 24 | data types sufficient for a library or application to code against to produce 25 | telemetry data, but does not actually collect, analyze, or export the data. 26 | 27 | To collect and analyze telemetry data, *applications* should also install a 28 | concrete implementation of the API. None exists at the moment, but work on 29 | this should start next. 30 | 31 | This separation allows *libraries* that produce telemetry data to depend only 32 | on the API, deferring the choice of concrete implementation to the application 33 | developer. 34 | 35 | ## How do I get started? 36 | 37 | Install this distribution from CPAN: 38 | ``` 39 | cpanm OpenTelemetry 40 | ``` 41 | or directly from the repository if you want to install a development 42 | version (although note that only the CPAN version is recommended for 43 | production environments): 44 | ``` 45 | # On a local fork 46 | cd path/to/this/repo 47 | cpanm install . 48 | 49 | # Over the net 50 | cpanm https://github.com/jjatria/perl-opentelemetry.git 51 | ``` 52 | 53 | Then, use the OpenTelemetry interfaces to produces traces and other telemetry 54 | data. Following is a basic example (although bear in mind this interface is 55 | still being drafted, so some details might change): 56 | 57 | ``` perl 58 | use OpenTelemetry; 59 | use v5.36; 60 | 61 | # Obtain the current default tracer provider 62 | my $provider = OpenTelemetry->tracer_provider; 63 | 64 | # Create a trace 65 | my $tracer = $provider->tracer( name => 'my_app', version => '1.0' ); 66 | 67 | # Record spans 68 | $tracer->in_span( outer => sub ( $span, $context ) { 69 | # In outer span 70 | 71 | $tracer->in_span( inner => sub ( $span, $context ) { 72 | # In inner span 73 | }); 74 | }); 75 | ``` 76 | 77 | ## How can I get involved? 78 | 79 | We are in the process of setting up an OpenTelemetry-Perl special interest 80 | group (SIG). Until that is set up, you are free to [express your 81 | interest][sig] or join us in IRC on the #io-async channel in irc.perl.org. 82 | 83 | ## License 84 | 85 | The OpenTelemetry distribution is licensed under the same terms as Perl 86 | itself. See [LICENSE] for more information. 87 | 88 | [an SDK implementation]: https://github.com/jjatria/perl-opentelemetry-sdk 89 | [Coverage Status]: https://coveralls.io/repos/github/jjatria/perl-opentelemetry/badge.svg?branch=main 90 | [coveralls]: https://coveralls.io/github/jjatria/perl-opentelemetry?branch=main 91 | [license]: https://github.com/jjatria/perl-opentelemetry/blob/main/LICENSE 92 | [sig]: https://github.com/open-telemetry/community/issues/828 93 | -------------------------------------------------------------------------------- /lib/OpenTelemetry/Propagator/TraceContext/TraceState.pm: -------------------------------------------------------------------------------- 1 | use Object::Pad; 2 | # ABSTRACT: Represents the TraceState in a W3C TraceContext 3 | 4 | package OpenTelemetry::Propagator::TraceContext::TraceState; 5 | 6 | our $VERSION = '0.034'; 7 | 8 | class OpenTelemetry::Propagator::TraceContext::TraceState { 9 | use List::Util; 10 | use OpenTelemetry::Common (); 11 | 12 | my $logger = OpenTelemetry::Common::internal_logger; 13 | 14 | my $MAX_MEMBERS = 32 * 2; # The member list is a flat list of pairs 15 | my $VALID_KEY = qr/ 16 | ^ 17 | (?: 18 | [ a-z ] [ a-z 0-9 _ * \/ - ]{0,255} # simple-key 19 | | (?: # multi-tenant-key 20 | [ a-z 0-9 ] [ a-z 0-9 _ * \/ - ]{0,240} # tenant-id 21 | @ 22 | [ a-z ] [ a-z 0-9 _ * \/ - ]{0,13} # system-id 23 | ) 24 | ) 25 | $ 26 | /xx; 27 | 28 | my $VALID_VALUE = qr/ 29 | ^ 30 | [ \x{20} \x{21}-\x{2B} \x{2D}-\x{3C} \x{3E}-\x{7E} ]{0,255} 31 | [ \x{21}-\x{2B} \x{2D}-\x{3C} \x{3E}-\x{7E} ] 32 | $ 33 | /xx; 34 | 35 | field @members; 36 | 37 | # Private methods 38 | 39 | method $init ( @new ) { 40 | @members = splice @new, 0, $MAX_MEMBERS; 41 | $self 42 | } 43 | 44 | # Internal setting method: assumes parameters are validated 45 | # Having this in a separate method means we can call it from an 46 | # instance other than $self (eg. the one returned by the call to 47 | # 'delete' in the public 'set' method) 48 | method $set ( $key, $value ) { 49 | unshift @members, $key => $value; 50 | @members = splice @members, 0, $MAX_MEMBERS if @members > $MAX_MEMBERS; 51 | $self; 52 | } 53 | 54 | # Public interface 55 | 56 | sub from_string ( $class, $string = undef ) { 57 | $logger->debug('Got an undefined value instead of a string when parsing TraceState') 58 | unless defined $string; 59 | 60 | my @members; 61 | for ( grep $_, split ',', $string // '' ) { 62 | my ( $key, $value ) = split '=', s/^\s+|\s+$//gr, 2; 63 | 64 | next unless $key =~ $VALID_KEY && $value =~ $VALID_VALUE; 65 | push @members, $key => $value; 66 | } 67 | 68 | $class->new->$init(@members); 69 | } 70 | 71 | method to_string () { 72 | join ',', List::Util::pairmap { join '=', $a, $b } @members 73 | } 74 | 75 | # Gets the value 76 | method get ( $key ) { 77 | my ( undef, $value ) = List::Util::pairfirst { $a eq $key } @members; 78 | $value; 79 | } 80 | 81 | # Sets a new pair, overwriting any existing one with the same key 82 | method set ( $key, $value ) { 83 | if ( $key !~ $VALID_KEY ) { 84 | $logger->debugf("Invalid TraceState member key: '%s' => '%s'", $key, $value ); 85 | return $self; 86 | } 87 | elsif ( $value !~ $VALID_VALUE ) { 88 | $logger->debugf("Invalid TraceState member value: '%s' => '%s'", $key, $value ); 89 | return $self; 90 | } 91 | 92 | $self->delete($key)->$set( $key => $value ); 93 | } 94 | 95 | # Returns a clone of the TraceState without the deleted key 96 | method delete ( $key ) { 97 | ( ref $self )->new->$init( 98 | List::Util::pairgrep { $a ne $key } @members 99 | ); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /t/OpenTelemetry/Constants.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use Test2::V0 -target => 'OpenTelemetry::Constants'; 4 | 5 | is \%OpenTelemetry::Constants::EXPORT_TAGS, { 6 | log => [qw( 7 | LOG_LEVEL_TRACE 8 | LOG_LEVEL_TRACE2 9 | LOG_LEVEL_TRACE3 10 | LOG_LEVEL_TRACE4 11 | LOG_LEVEL_DEBUG 12 | LOG_LEVEL_DEBUG2 13 | LOG_LEVEL_DEBUG3 14 | LOG_LEVEL_DEBUG4 15 | LOG_LEVEL_INFO 16 | LOG_LEVEL_INFO2 17 | LOG_LEVEL_INFO3 18 | LOG_LEVEL_INFO4 19 | LOG_LEVEL_WARN 20 | LOG_LEVEL_WARN2 21 | LOG_LEVEL_WARN3 22 | LOG_LEVEL_WARN4 23 | LOG_LEVEL_ERROR 24 | LOG_LEVEL_ERROR2 25 | LOG_LEVEL_ERROR3 26 | LOG_LEVEL_ERROR4 27 | LOG_LEVEL_FATAL 28 | LOG_LEVEL_FATAL2 29 | LOG_LEVEL_FATAL3 30 | LOG_LEVEL_FATAL4 31 | )], 32 | span => [qw( 33 | INVALID_SPAN_ID 34 | HEX_INVALID_SPAN_ID 35 | SPAN_STATUS_UNSET 36 | SPAN_STATUS_OK 37 | SPAN_STATUS_ERROR 38 | SPAN_KIND_INTERNAL 39 | SPAN_KIND_SERVER 40 | SPAN_KIND_CLIENT 41 | SPAN_KIND_PRODUCER 42 | SPAN_KIND_CONSUMER 43 | )], 44 | span_kind => [qw( 45 | SPAN_KIND_INTERNAL 46 | SPAN_KIND_SERVER 47 | SPAN_KIND_CLIENT 48 | SPAN_KIND_PRODUCER 49 | SPAN_KIND_CONSUMER 50 | )], 51 | span_status => [qw( 52 | SPAN_STATUS_UNSET 53 | SPAN_STATUS_OK 54 | SPAN_STATUS_ERROR 55 | )], 56 | trace => [qw( 57 | INVALID_TRACE_ID 58 | HEX_INVALID_TRACE_ID 59 | TRACE_EXPORT_SUCCESS 60 | TRACE_EXPORT_FAILURE 61 | TRACE_EXPORT_TIMEOUT 62 | )], 63 | trace_export => [qw( 64 | TRACE_EXPORT_SUCCESS 65 | TRACE_EXPORT_FAILURE 66 | TRACE_EXPORT_TIMEOUT 67 | )], 68 | export => [qw( 69 | EXPORT_RESULT_SUCCESS 70 | EXPORT_RESULT_FAILURE 71 | EXPORT_RESULT_TIMEOUT 72 | )], 73 | }, 'Export tags are correct'; 74 | 75 | is \@OpenTelemetry::Constants::EXPORT_OK, [qw( 76 | EXPORT_RESULT_SUCCESS 77 | EXPORT_RESULT_FAILURE 78 | EXPORT_RESULT_TIMEOUT 79 | INVALID_TRACE_ID 80 | HEX_INVALID_TRACE_ID 81 | TRACE_EXPORT_SUCCESS 82 | TRACE_EXPORT_FAILURE 83 | TRACE_EXPORT_TIMEOUT 84 | INVALID_SPAN_ID 85 | HEX_INVALID_SPAN_ID 86 | SPAN_STATUS_UNSET 87 | SPAN_STATUS_OK 88 | SPAN_STATUS_ERROR 89 | SPAN_KIND_INTERNAL 90 | SPAN_KIND_SERVER 91 | SPAN_KIND_CLIENT 92 | SPAN_KIND_PRODUCER 93 | SPAN_KIND_CONSUMER 94 | LOG_LEVEL_TRACE 95 | LOG_LEVEL_TRACE2 96 | LOG_LEVEL_TRACE3 97 | LOG_LEVEL_TRACE4 98 | LOG_LEVEL_DEBUG 99 | LOG_LEVEL_DEBUG2 100 | LOG_LEVEL_DEBUG3 101 | LOG_LEVEL_DEBUG4 102 | LOG_LEVEL_INFO 103 | LOG_LEVEL_INFO2 104 | LOG_LEVEL_INFO3 105 | LOG_LEVEL_INFO4 106 | LOG_LEVEL_WARN 107 | LOG_LEVEL_WARN2 108 | LOG_LEVEL_WARN3 109 | LOG_LEVEL_WARN4 110 | LOG_LEVEL_ERROR 111 | LOG_LEVEL_ERROR2 112 | LOG_LEVEL_ERROR3 113 | LOG_LEVEL_ERROR4 114 | LOG_LEVEL_FATAL 115 | LOG_LEVEL_FATAL2 116 | LOG_LEVEL_FATAL3 117 | LOG_LEVEL_FATAL4 118 | )], 'Exportable functions are correct'; 119 | 120 | is \@OpenTelemetry::EXPORT, [], 'Exports nothing by default'; 121 | 122 | done_testing; 123 | -------------------------------------------------------------------------------- /t/OpenTelemetry/Trace/Tracer.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use Test2::V0 -target => 'OpenTelemetry::Trace::Tracer'; 4 | use Test2::Tools::OpenTelemetry; 5 | 6 | use experimental 'signatures'; 7 | 8 | use OpenTelemetry::Constants -span; 9 | 10 | is my $tracer = CLASS->new, object { 11 | prop isa => 'OpenTelemetry::Trace::Tracer'; 12 | }, 'Can construct tracer'; 13 | 14 | is $tracer->create_span( name => 'span' ), object { 15 | prop isa => 'OpenTelemetry::Trace::Span'; 16 | call context => object { call valid => F }; 17 | }, 'Creates an invalid span'; 18 | 19 | subtest 'Convenience in_span method' => sub { 20 | my $todo = todo 'Experimental API'; 21 | 22 | use OpenTelemetry::Trace; 23 | 24 | my $mock = mock $tracer => override => [ 25 | create_span => sub ( $, %args ) { 26 | mock \%args => track => 1 => add => [ 27 | status => sub { 28 | mock obj => add => [ 29 | is_unset => sub { 30 | !( defined $args{status} ) 31 | || $args{status} == SPAN_STATUS_UNSET; 32 | }, 33 | ]; 34 | }, 35 | set_status => sub ( $span, $status, @ ){ 36 | $span->{status} = $status; 37 | } 38 | ]; 39 | }, 40 | ]; 41 | 42 | my ( $ret, $mocked ); 43 | no_messages { 44 | $ret = $tracer->in_span( some_span => sub ( $span, $context ) { 45 | ref_is + OpenTelemetry::Trace->span_from_context($context), 46 | $span, 47 | 'Received a context with the span'; 48 | 49 | ($mocked) = mocked $span; 50 | 51 | return 'TEST'; 52 | }); 53 | }; 54 | 55 | is $mocked->call_tracking, [ 56 | { 57 | sub_name => 'set_status', 58 | args => [ D, SPAN_STATUS_OK ], 59 | sub_ref => E, 60 | }, 61 | { sub_name => 'end', 62 | args => [ D ], 63 | sub_ref => E, 64 | }, 65 | ], 'Set status and ended at end of block'; 66 | 67 | is $ret, 'TEST', 'in_span returns what the block returns'; 68 | 69 | like dies { $tracer->in_span('name') }, 70 | qr/^Missing required code block /, 'Requires code block'; 71 | 72 | like dies { $tracer->in_span( sub { } ) }, 73 | qr/^Missing required span name/, 'Requires span name'; 74 | 75 | no_messages { 76 | is [ $tracer->in_span( foo => sub { qw( a b c ) } ) ], 77 | [qw( a b c )], 'Can return list context'; 78 | }; 79 | 80 | no_messages { 81 | like dies { 82 | $tracer->in_span( 83 | dead_span => sub ( $span, $context ) { 84 | ($mocked) = mocked $span; 85 | die 'An error'; 86 | }, 87 | ); 88 | } => qr/^An error/, 'If sub dies, exception is not caught'; 89 | }; 90 | 91 | is $mocked->call_tracking, [ 92 | { 93 | sub_name => 'record_exception', 94 | args => [ D, match qr/^An error/ ], 95 | sub_ref => E 96 | }, 97 | { 98 | sub_name => 'set_status', 99 | args => [ D, SPAN_STATUS_ERROR, match qr/^An error/ ], 100 | sub_ref => E 101 | }, 102 | { 103 | sub_name => 'end', 104 | args => [ D ], 105 | sub_ref => E 106 | }, 107 | ], 'Span records caught error'; 108 | }; 109 | 110 | done_testing; 111 | -------------------------------------------------------------------------------- /lib/OpenTelemetry/Exporter.pod: -------------------------------------------------------------------------------- 1 | =encoding UTF-8 2 | 3 | =head1 NAME 4 | 5 | OpenTelemetry::Exporter - Abstract interface of an OpenTelemetry exporter 6 | 7 | =head1 SYNOPSIS 8 | 9 | use Object::Pad; 10 | use OpenTelemetry::SDK::Trace::Span::Processor::Batch; 11 | use Future::AsyncAwait; 12 | 13 | class My::Exporter :does(OpenTelemetry::Exporter) { 14 | method export ( $elements, $timeout // undef ) { ... } 15 | 16 | async method shutdown ( $timeout // undef ) { ... } 17 | async method force_flush ( $timeout // undef ) { ... } 18 | } 19 | 20 | # The exporter interface is the same for exporters regardless of what 21 | # they export. 22 | 23 | # Attach it to a processor 24 | my $processor = Some::Processor->new( exporter => My::Exporter->new ); 25 | 26 | # Register the processor with a provider 27 | $provider->add_span_processor($processor); 28 | 29 | =head1 DESCRIPTION 30 | 31 | This module provides an abstract role that can be used by classes implementing 32 | OpenTelemetry exporters. Exporters are objects that can take telemetry data, 33 | and send it to some external target. 34 | 35 | Exporters receive the data they export from a processor, which must 36 | implement the interface defined in L. 37 | 38 | Although this cannot be enforced in the code, the methods described in this 39 | role are all expected to return one of the values from 40 | L. 41 | 42 | =head1 METHODS 43 | 44 | =head2 export 45 | 46 | $result = $exporter->export( \@elements, $timeout // undef ); 47 | 48 | Takes an array reference with exportable elements (such as readable spans 49 | like L or log records like 50 | L) and an optional timeout value, 51 | and returns the outcome of exporting the span data. 52 | 53 | The return value will be one of the 54 | L. 55 | 56 | =head2 shutdown 57 | 58 | $result = await $exporter->shutdown( $timeout // undef ); 59 | 60 | Takes an optional timeout value and returns a L that will be done 61 | when this exporter has completed shutting down. The shutdown process must 62 | include the effects of L, described below. After shutting down, 63 | the exporter is not expected to do any further work, and should ignore any 64 | subsequent calls. 65 | 66 | The value of the future will be one of the 67 | L. 68 | 69 | =head2 force_flush 70 | 71 | $result = await $exporter->force_flush( $timeout // undef ); 72 | 73 | Takes an optional timeout value and returns a L that will be done 74 | when this exporter has finished flushing. Flushing signals to the exporter 75 | that it should export the data for any unprocessed spans as soon as possible. 76 | This could be due to an imminent shutdown, but does not have to be. 77 | 78 | The value of the future will be one of the 79 | L. 80 | 81 | =head1 SEE ALSO 82 | 83 | =over 84 | 85 | =item L 86 | 87 | =item L 88 | 89 | =item L 90 | 91 | =item L 92 | 93 | =item L 94 | 95 | =back 96 | 97 | =head1 COPYRIGHT AND LICENSE 98 | 99 | This software is copyright (c) 2023 by José Joaquín Atria. 100 | 101 | This is free software; you can redistribute it and/or modify it under the same 102 | terms as the Perl 5 programming language system itself. 103 | -------------------------------------------------------------------------------- /lib/OpenTelemetry/Common.pod: -------------------------------------------------------------------------------- 1 | =encoding UTF-8 2 | 3 | =head1 NAME 4 | 5 | OpenTelemetry::Common - Utility package with shared functions for OpenTelemetry 6 | 7 | =head1 SYNOPSIS 8 | 9 | use OpenTelemetry::Common; 10 | 11 | # Read a config value from the environment for the FOO and BAR keys 12 | # or undefined if they are not set 13 | my $value = config(qw( FOO BAR )) // $default; 14 | 15 | my $timeout = 10; # seconds 16 | my $start = timeout_timestamp; 17 | 18 | for (@tasks) { 19 | last unless maybe_timeout $timeout, $start; 20 | 21 | ... 22 | } 23 | 24 | =head1 DESCRIPTION 25 | 26 | This module contains functions that are useful throughout the OpenTelemetry 27 | codebase, and help provide a consistent way to handle certain processes. 28 | 29 | They will most likely only be of interest to authors of OpenTelemetry 30 | libraries and instrumentation. 31 | 32 | =head1 FUNCTIONS 33 | 34 | =head2 config 35 | 36 | $value = config(@keys); 37 | 38 | This function takes a list of keys and tries to read values from those keys 39 | in the environment, and return the first value it finds, or undefined if no 40 | value is found. 41 | 42 | Keys will be read from the environment in order, after prepending first the 43 | C prefix (which should be specific to this Perl implementation), 44 | and then the C prefix (which are the ones defined by the standard) if 45 | no value has been found. 46 | 47 | Values will be parsed as described in 48 | L, which in the context of Perl means the following: 49 | 50 | =over 51 | 52 | =item * 53 | 54 | Empty values are the same as undefined 55 | 56 | =item * 57 | 58 | If the variable is set to the string "true" or "false" (case-insensitively), 59 | it will be returned as a true or false value respectively 60 | 61 | =item * 62 | 63 | Any other value will be returned as is 64 | 65 | =back 66 | 67 | =head2 maybe_timeout 68 | 69 | $time_remaining = maybe_timeout( 70 | $timeout // undef, 71 | $start // 0, 72 | ); 73 | 74 | Takes a timeout value and an optional starting timestamp, and returns how 75 | much of the provided timeout is still available. This is useful for timed 76 | operations that span over several steps, and need to propagate this timeout 77 | to additional calls. 78 | 79 | If the timeout argument was undefined, this function returns undefined, to 80 | indicate the absence of a timeout (rather than the fact that time has 81 | run out). 82 | 83 | =head2 timeout_timestamp 84 | 85 | $timestamp = timeout_timestamp; 86 | 87 | Returns a monotonic timestamp value. This is used internally in 88 | L, described above. 89 | 90 | =head2 generate_span_id 91 | 92 | $id = generate_span_id; 93 | 94 | Generates a new random span ID. Do not use this function directly. Use it 95 | instead through the interface provided in 96 | L. 97 | 98 | =head2 generate_trace_id 99 | 100 | $id = generate_trace_id; 101 | 102 | Generates a new random trace ID. Do not use this function directly. Use it 103 | instead through the interface provided in 104 | L. 105 | 106 | =head1 COPYRIGHT AND LICENSE 107 | 108 | This software is copyright (c) 2023 by José Joaquín Atria. 109 | 110 | This is free software; you can redistribute it and/or modify it under the same 111 | terms as the Perl 5 programming language system itself. 112 | -------------------------------------------------------------------------------- /lib/OpenTelemetry/Trace/TracerProvider.pod: -------------------------------------------------------------------------------- 1 | =encoding UTF-8 2 | 3 | =head1 NAME 4 | 5 | OpenTelemetry::Trace::TracerProvider - Provides access to OpenTelemetry Tracers 6 | 7 | =head1 SYNOPSIS 8 | 9 | use OpenTelemetry; 10 | 11 | # Read the globally set provider 12 | my $provider = OpenTelemetry->tracer_provider; 13 | my $tracer = $provider->tracer; 14 | my $span = $tracer->create_span( name => 'My span' ); 15 | 16 | # Set a global tracer provider 17 | OpenTelemetry->tracer_provider = $another_provider; 18 | 19 | =head1 DESCRIPTION 20 | 21 | As implied by its name, the tracer provider is responsible for providing 22 | access to a usable instance of L, which can in 23 | turn be used to create L instances to mark the 24 | scope of an operation. 25 | 26 | The provider implemented in this package returns an instance of 27 | L which is cached internally. This behaviour 28 | can be modified by inheriting from this class and providing a different 29 | implementation of the L method described below. See 30 | L for a way to set this modified version as a 31 | globally available tracer provider. 32 | 33 | =head1 METHODS 34 | 35 | =head2 new 36 | 37 | $provider = OpenTelemetry::Trace::TracerProvider->new 38 | 39 | Creates a new instance of the tracer provider. 40 | 41 | =head2 tracer 42 | 43 | $tracer = $trace_provider->tracer( %args ) 44 | 45 | Takes a set of named parameters, and returns a tracer that can be used to 46 | generate spans via L. Accepted 47 | parameters are: 48 | 49 | =over 50 | 51 | =item C 52 | 53 | A name that uniquely identifies an 54 | L. This can 55 | be the instrumentation library, a package name, etc. This value I be 56 | set to a non-empty string. 57 | 58 | =item C 59 | 60 | Specifies the version of the 61 | L, if one is 62 | available. 63 | 64 | =item C 65 | 66 | A hash reference with a set of attributes for this 67 | L. 68 | 69 | =item C 70 | 71 | The schema URL to be recorded in the emitted telemetry. 72 | 73 | =back 74 | 75 | The code implemented in this package ignores all arguments and returns a 76 | L, but subclasses (most notably 77 | L) are free to modify this. 78 | 79 | Callers are free to cache this tracer, which tracer providers must ensure 80 | can continue to work. In the event that the configuration of the tracer 81 | provider has changed, it is the responsibility of the provider to 82 | propagate these changes to existing tracers, or to ensure that existing 83 | tracers remain usable. 84 | 85 | That said, callers should be aware that tracer providers I change, 86 | even in limited scopes, and while the tracer provider is responsible for 87 | looking after the tracers it has generated, they are not required (and may 88 | not be capable) to alter the functioning of tracers that have been created 89 | by other providers. 90 | 91 | If creating the tracer is expensive, then it's the tracer provider's 92 | responsibility to cache it. 93 | 94 | =head1 COPYRIGHT AND LICENSE 95 | 96 | This software is copyright (c) 2023 by José Joaquín Atria. 97 | 98 | This is free software; you can redistribute it and/or modify it under the same 99 | terms as the Perl 5 programming language system itself. 100 | -------------------------------------------------------------------------------- /lib/OpenTelemetry/Logs/Logger.pod: -------------------------------------------------------------------------------- 1 | =encoding UTF-8 2 | 3 | =head1 NAME 4 | 5 | OpenTelemetry::Logs::Logger - A log record factory for OpenTelemetry 6 | 7 | =head1 SYNOPSIS 8 | 9 | use OpenTelemetry; 10 | 11 | my $provider = OpenTelemetry->logger_provider; 12 | my $logger = $provider->logger; 13 | 14 | # Emit a log record 15 | $logger->emit_record(%args); 16 | 17 | =head1 DESCRIPTION 18 | 19 | A logger is responsible for emitting log records. The class provided by this 20 | module does nothing, but is suitable to be sub-classed to emit specific kinds 21 | of logs. See L for one such example which 22 | emits L objects. 23 | 24 | =head1 METHODS 25 | 26 | =head2 emit_record 27 | 28 | $logger->emit_record( 29 | attributes => %attributes // {}, 30 | body => $body, 31 | context => $context // undef, 32 | observed_timestamp => $timestamp // time, 33 | severity_number => $number // undef, 34 | severity_text => $text // undef, 35 | timestamp => $timestamp // undef, 36 | ) 37 | 38 | Emits a log record. 39 | 40 | Takes a list of key / value pairs. Of these, the only one that callers are 41 | I expected to provide is the log record body: not doing so may result 42 | in a warning being logged. All other keys are optional and can be used to 43 | further specify the record. The value of the C parameter is not required 44 | to be a string, in order to support structured loggers. 45 | 46 | If provided, the value of the C parameter should be an instance of 47 | L. Otherwise, the current context will be used. In 48 | either case, this will be used to access the span context this log record 49 | should be associated to, if any. 50 | 51 | The value of the C parameter represents the time at which the 52 | event the log record is associated with took place. This can be left unset 53 | if no such time is known. This is different from the C, 54 | which is the time at which the record was seen by the collection platform. 55 | If this is not provided, it will be given a default value by the platform. 56 | 57 | The value of the C parameter should be set to the name of 58 | the level of the logged event (eg. "error", "warning", etc) as known at the 59 | source. The value of the C, on the other hand, should be 60 | a number between 1 and 24, with numbers mapping to the following levels: 61 | 62 | =over 63 | 64 | =item TRACE 65 | 66 | Levels 1-4. A fine-grained debugging event. Typically disabled in default 67 | configurations. 68 | 69 | =item DEBUG 70 | 71 | Levels 5-8. A debugging event. 72 | 73 | =item INFO 74 | 75 | Levels 9-12. An informational event. Indicates that an event happened. 76 | 77 | =item WARN 78 | 79 | Levels 13-16. A warning event. Not an error but is likely more important 80 | than an informational event. 81 | 82 | =item ERROR 83 | 84 | Levels 17-20. An error event. Something went wrong. 85 | 86 | =item FATAL 87 | 88 | Levels 21-24. A fatal error such as application or system crash. 89 | 90 | =back 91 | 92 | Values in L are suitable to 93 | be used as values for C. They will be evaluated in numeric 94 | context internally. 95 | 96 | =head1 COPYRIGHT AND LICENSE 97 | 98 | This software is copyright (c) 2024 by José Joaquín Atria. 99 | 100 | This is free software; you can redistribute it and/or modify it under the same 101 | terms as the Perl 5 programming language system itself. 102 | -------------------------------------------------------------------------------- /lib/Log/Any/Adapter/OpenTelemetry.pm: -------------------------------------------------------------------------------- 1 | package Log::Any::Adapter::OpenTelemetry; 2 | 3 | use strict; 4 | use warnings; 5 | use experimental 'signatures'; 6 | 7 | our $VERSION = '0.034'; 8 | 9 | use Log::Any::Adapter::Util (); 10 | use OpenTelemetry qw( otel_config otel_span_from_context otel_logger_provider ); 11 | use Ref::Util 'is_hashref'; 12 | use Time::HiRes 'time'; 13 | 14 | use OpenTelemetry::Constants qw( 15 | LOG_LEVEL_TRACE 16 | LOG_LEVEL_DEBUG 17 | LOG_LEVEL_INFO 18 | LOG_LEVEL_WARN 19 | LOG_LEVEL_ERROR 20 | LOG_LEVEL_FATAL 21 | ); 22 | 23 | use base 'Log::Any::Adapter::Base'; 24 | 25 | my %LOG2OTEL = ( 26 | trace => LOG_LEVEL_TRACE, 27 | debug => LOG_LEVEL_DEBUG, 28 | info => LOG_LEVEL_INFO, 29 | warn => LOG_LEVEL_WARN, 30 | error => LOG_LEVEL_ERROR, 31 | fatal => LOG_LEVEL_FATAL, 32 | ); 33 | 34 | my %OTEL2LOG = ( 35 | trace => 8, 36 | debug => 7, 37 | info => 6, 38 | warn => 4, 39 | error => 3, 40 | fatal => 2, 41 | ); 42 | 43 | sub init ( $self, @ ) { 44 | # FIXME: It would be good to get a logger early and cache 45 | # it for eventual calls. However, this suffers from the same 46 | # issue with caching tracers that is documented in the POD 47 | # for OpenTelemetry::Trace::Tracer: namely, that if we get 48 | # the no-op logger before we've set up a real logger provider 49 | # that can generate real loggers, we'll be stuck with a no-op. 50 | # It might be that we need to revisit the proxy classes removed 51 | # in d9e321bd1bf65d510b12ef34fe2b5a0c51da0bf2, although the 52 | # rationale for why they were removed is still sound. We'd just 53 | # have to come up with a way to make sure its delegate continues 54 | # to point to the right place even if the tracer provider changes 55 | # $self->{logger} = otel_logger_provider->logger; 56 | } 57 | 58 | for my $method ( Log::Any::Adapter::Util::logging_methods() ) { 59 | no strict 'refs'; 60 | *$method = sub ( $self, @args) { 61 | $self->structured( $method, $self->category, @args ); 62 | }; 63 | } 64 | 65 | for my $method ( Log::Any::Adapter::Util::detection_methods() ) { 66 | my $numeric = Log::Any::Adapter::Util::numeric_level( $method =~ s/^is_//r ); 67 | 68 | no strict 'refs'; 69 | *$method = sub { 70 | my $level = $OTEL2LOG{ lc( otel_config('LOG_LEVEL') // 'info' ) }; 71 | $numeric <= ( $level // $OTEL2LOG{info} ); 72 | }; 73 | } 74 | 75 | sub structured ( $self, $method, $category, @parts ) { 76 | my $level = $method; 77 | for ($level) { 78 | s/(?:emergency|alert|critical)/fatal/; 79 | s/notice/info/; 80 | s/warning/warn/; 81 | } 82 | 83 | # FIXME: This is a little finicky. The aim is for the first 84 | # argument to be the body (even if it is structured), and 85 | # anything else gets put into the attributes. If the log 86 | # comes with structured data that is not a hash, we put it 87 | # under a `payload` key. Maybe this can be simplified to 88 | # always put the data under a given key, but then we add 89 | # data to the arguably common operation of attaching a hash. 90 | my %args = ( body => shift @parts ); 91 | 92 | $args{attributes} = @parts == 1 93 | ? is_hashref $parts[0] 94 | ? $parts[0] : { payload => $parts[0] } 95 | : @parts % 2 96 | ? { payload => \@parts } : { @parts } 97 | if @parts; 98 | 99 | otel_logger_provider->logger->emit_record( 100 | timestamp => time, 101 | severity_text => $method, 102 | severity_number => 0+$LOG2OTEL{$level}, 103 | %args, 104 | ); 105 | } 106 | 107 | 1; 108 | -------------------------------------------------------------------------------- /lib/OpenTelemetry/Logs/LogRecord/Processor.pod: -------------------------------------------------------------------------------- 1 | =encoding UTF-8 2 | 3 | =head1 NAME 4 | 5 | OpenTelemetry::Logs::LogRecord::Processor - Abstract interface of an OpenTelemetry log record processor 6 | 7 | =head1 SYNOPSIS 8 | 9 | use Object::Pad; 10 | use Future::AsyncAwait; 11 | 12 | class My::Processor :does(OpenTelemetry::Logs::LogRecord::Processor) { 13 | method on_emit ( $log_record ) { ... } 14 | } 15 | 16 | # Create it 17 | my $processor = My::Processor->new( ... ); 18 | 19 | # Register it with the OpenTelemetry tracer provider 20 | OpenTelemetry->logger_provider->add_log_record_processor($processor); 21 | 22 | =head1 DESCRIPTION 23 | 24 | This module provides an abstract role that can be used by classes implementing 25 | OpenTelemetry log record processors. Log record processors are objects that are 26 | registered with a L (or, more accurately, 27 | with one of its subclasses such as those provided by an OpenTelemetry SDK) to 28 | perform additional operations on log records when they are emitted. 29 | 30 | The processor is considered to be the start of a pipeline, which must be 31 | allowed to end with a L to export the log records 32 | to some collector. To this end, processors are expected to accept an optional 33 | C parameter to their constructor. But processors can be used for 34 | other ends. 35 | 36 | =head1 METHODS 37 | 38 | =head2 new 39 | 40 | $processor = Class::Implementing::This::Role->new( 41 | exporter => ..., # optional 42 | ... 43 | ); 44 | 45 | Should take an optional exporter set to an instance of a class implementing 46 | the L role. Processor classes are free to accept any 47 | other parameters they choose. 48 | 49 | =head2 on_emit 50 | 51 | $processor->on_emit( $log_record ); 52 | 53 | Called when the log record is emitted. It takes the emitted log record as its 54 | only parameter. 55 | 56 | This method is called synchronously when the log record is emitted, so it 57 | should not block or die. 58 | 59 | The return value of this method is ignored. 60 | 61 | =head2 shutdown 62 | 63 | $result = await $processor->shutdown( $timeout // undef ); 64 | 65 | Takes an optional timeout value and returns a L that will be done 66 | when this log record processor has completed shutting down. The shutdown 67 | process must include the effects of L, described below. After 68 | shutting down, the processor is not expected to do any further work, and 69 | should ignore any subsequent calls. 70 | 71 | The value of the future will be one of the 72 | L. 73 | 74 | =head2 force_flush 75 | 76 | $result = await $processor->force_flush( $timeout // undef ); 77 | 78 | Takes an optional timeout value and returns a L that will be done 79 | when this log record processor has finished flushing. Flushing signals to the 80 | processor that it should process the data for any unprocessed elements as soon 81 | as possible. This could be due to an imminent shutdown, but does not have to 82 | be. 83 | 84 | The value of the future will be one of the 85 | L. 86 | 87 | =head1 SEE ALSO 88 | 89 | =over 90 | 91 | =item L 92 | 93 | =item L 94 | 95 | =item L 96 | 97 | =item L 98 | 99 | =item L 100 | 101 | =item L 102 | 103 | =back 104 | 105 | =head1 COPYRIGHT AND LICENSE 106 | 107 | This software is copyright (c) 2024 by José Joaquín Atria. 108 | 109 | This is free software; you can redistribute it and/or modify it under the same 110 | terms as the Perl 5 programming language system itself. 111 | -------------------------------------------------------------------------------- /lib/OpenTelemetry/Propagator/Baggage.pm: -------------------------------------------------------------------------------- 1 | use Object::Pad; 2 | # ABSTRACT: Propagate context using the W3C Baggage format 3 | 4 | package OpenTelemetry::Propagator::Baggage; 5 | 6 | our $VERSION = '0.034'; 7 | 8 | class OpenTelemetry::Propagator::Baggage :does(OpenTelemetry::Propagator) { 9 | use OpenTelemetry; 10 | use Feature::Compat::Try; 11 | use OpenTelemetry::Baggage; 12 | use OpenTelemetry::Common (); 13 | use OpenTelemetry::Context; 14 | use OpenTelemetry::Propagator::TextMap; 15 | use URL::Encode qw( url_decode_utf8 url_encode_utf8 ); 16 | 17 | use isa 'OpenTelemetry::X'; 18 | 19 | my $KEY = 'baggage'; 20 | my $MAX_ENTRIES = 180; 21 | my $MAX_ENTRY_LENGTH = 4096; 22 | my $MAX_TOTAL_LENGTH = 8192; 23 | 24 | my $logger = OpenTelemetry::Common::internal_logger; 25 | 26 | method $encode (%baggage) { 27 | my $encoded = ''; 28 | my $total = 0; 29 | 30 | for my $key ( keys %baggage ) { 31 | last if $total > $MAX_ENTRIES; 32 | 33 | next unless $baggage{$key}; 34 | my $entry = join '=', 35 | url_encode_utf8($key), 36 | url_encode_utf8( $baggage{$key}->value ); 37 | 38 | my $length = length $entry; 39 | next unless $length < $MAX_ENTRY_LENGTH 40 | && $length + length($encoded) < $MAX_TOTAL_LENGTH; 41 | 42 | $encoded .= ',' if $encoded; 43 | $encoded .= $entry; 44 | 45 | if ( my $meta = $baggage{$key}->meta ) { 46 | $encoded .= ";$meta"; 47 | } 48 | 49 | $total++; 50 | } 51 | 52 | $encoded; 53 | } 54 | 55 | method inject ( 56 | $carrier, 57 | $context = undef, 58 | $setter = undef 59 | ) { 60 | $context //= OpenTelemetry::Context->current; 61 | $setter //= OpenTelemetry::Propagator::TextMap::SETTER; 62 | 63 | try { 64 | my %baggage = OpenTelemetry::Baggage->all($context); 65 | return $self unless %baggage; 66 | 67 | my $encoded = $self->$encode(%baggage); 68 | $setter->( $carrier, $KEY, $encoded ) if $encoded; 69 | } 70 | catch($e) { 71 | if ( isa_OpenTelemetry_X $e ) { $logger->warn($e->get_message) } 72 | else { 73 | OpenTelemetry->handle_error( 74 | exception => $e, 75 | message => 'Error while injecting baggage', 76 | ); 77 | } 78 | } 79 | 80 | return $self; 81 | } 82 | 83 | method extract ( 84 | $carrier, 85 | $context = undef, 86 | $getter = undef 87 | ) { 88 | $context //= OpenTelemetry::Context->current; 89 | $getter //= OpenTelemetry::Propagator::TextMap::GETTER; 90 | 91 | try { 92 | my $header = $carrier->$getter($KEY) or return $context; 93 | 94 | my $builder = OpenTelemetry::Baggage->builder; 95 | 96 | for ( split ',', $header =~ s/\s//gr ) { 97 | my ( $kv, $meta ) = split ';', $_, 2; 98 | my ( $key, $value ) = map url_decode_utf8($_), split '=', $kv, 2; 99 | $builder->set( $key, $value, $meta ); 100 | } 101 | 102 | $builder->build($context); 103 | } 104 | catch($e) { 105 | if ( isa_OpenTelemetry_X $e ) { $logger->warn($e->get_message) } 106 | else { 107 | OpenTelemetry->handle_error( 108 | exception => $e, 109 | message => 'Error while extracting baggage', 110 | ); 111 | } 112 | 113 | return $context; 114 | } 115 | } 116 | 117 | method keys () { $KEY } 118 | } 119 | -------------------------------------------------------------------------------- /lib/OpenTelemetry/Propagator/TraceContext.pm: -------------------------------------------------------------------------------- 1 | use Object::Pad; 2 | # ABSTRACT: Propagate context using the W3C TraceContext format 3 | 4 | package OpenTelemetry::Propagator::TraceContext; 5 | 6 | our $VERSION = '0.034'; 7 | 8 | class OpenTelemetry::Propagator::TraceContext :does(OpenTelemetry::Propagator) { 9 | use isa 'OpenTelemetry::X'; 10 | 11 | use Feature::Compat::Try; 12 | use URL::Encode qw( url_decode_utf8 url_encode_utf8 ); 13 | 14 | use OpenTelemetry; 15 | use OpenTelemetry::Common (); 16 | use OpenTelemetry::Propagator::TextMap; 17 | use OpenTelemetry::Propagator::TraceContext::TraceParent; 18 | use OpenTelemetry::Propagator::TraceContext::TraceState; 19 | use OpenTelemetry::Trace::SpanContext; 20 | use OpenTelemetry::Trace; 21 | 22 | my $TRACE_PARENT_KEY = 'traceparent'; 23 | my $TRACE_STATE_KEY = 'tracestate'; 24 | 25 | my $logger = OpenTelemetry::Common::internal_logger; 26 | 27 | method inject ( 28 | $carrier, 29 | $context = undef, 30 | $setter = undef 31 | ) { 32 | $context //= OpenTelemetry::Context->current; 33 | $setter //= OpenTelemetry::Propagator::TextMap::SETTER; 34 | 35 | try { 36 | my $span_context = OpenTelemetry::Trace 37 | ->span_from_context($context) 38 | ->context; 39 | 40 | return $self unless $span_context->valid; 41 | 42 | my $trace_parent = OpenTelemetry::Propagator::TraceContext::TraceParent 43 | ->from_span_context($span_context); 44 | 45 | $setter->( $carrier, $TRACE_PARENT_KEY, $trace_parent->to_string ); 46 | $setter->( $carrier, $TRACE_STATE_KEY, $span_context->trace_state->to_string ); 47 | } 48 | catch($e) { 49 | if ( isa_OpenTelemetry_X $e ) { $logger->warn($e->get_message) } 50 | else { 51 | OpenTelemetry->handle_error( 52 | exception => $e, 53 | message => 'Error while injecting trace context', 54 | ); 55 | } 56 | } 57 | 58 | return $self; 59 | } 60 | 61 | method extract ( 62 | $carrier, 63 | $context = undef, 64 | $getter = undef 65 | ) { 66 | $context //= OpenTelemetry::Context->current; 67 | $getter //= OpenTelemetry::Propagator::TextMap::GETTER; 68 | 69 | try { 70 | my $string = $getter->( $carrier, $TRACE_PARENT_KEY ) 71 | or return $context; 72 | 73 | my $trace_parent = OpenTelemetry::Propagator::TraceContext::TraceParent 74 | ->from_string($string); 75 | 76 | my $trace_state = OpenTelemetry::Propagator::TraceContext::TraceState 77 | ->from_string( $getter->( $carrier, $TRACE_STATE_KEY ) // '' ); 78 | 79 | my $span_context = OpenTelemetry::Trace::SpanContext->new( 80 | trace_id => $trace_parent->trace_id, 81 | span_id => $trace_parent->span_id, 82 | trace_flags => $trace_parent->trace_flags, 83 | trace_state => $trace_state, 84 | remote => 1, 85 | ); 86 | 87 | my $span = OpenTelemetry::Trace->non_recording_span( $span_context ); 88 | 89 | return OpenTelemetry::Trace->context_with_span( $span, $context ); 90 | } 91 | catch ($e) { 92 | if ( isa_OpenTelemetry_X $e ) { $logger->warn($e->get_message) } 93 | else { 94 | OpenTelemetry->handle_error( 95 | exception => $e, 96 | message => 'Error while extracting trace context', 97 | ); 98 | } 99 | 100 | return $context; 101 | } 102 | } 103 | 104 | method keys () { ( $TRACE_PARENT_KEY, $TRACE_STATE_KEY ) } 105 | } 106 | -------------------------------------------------------------------------------- /lib/OpenTelemetry/Attributes.pm: -------------------------------------------------------------------------------- 1 | use Object::Pad ':experimental(init_expr)'; 2 | # ABSTRACT: A class encapsulating attribute validation for OpenTelemetry 3 | 4 | package OpenTelemetry::Attributes; 5 | 6 | our $VERSION = '0.034'; 7 | 8 | class OpenTelemetry::AttributeMap { 9 | use Log::Any; 10 | use OpenTelemetry::Common (); 11 | 12 | my $logger = OpenTelemetry::Common::internal_logger; 13 | 14 | use List::Util qw( any pairs ); 15 | use Ref::Util qw( is_hashref is_arrayref ); 16 | use Storable 'dclone'; 17 | 18 | field $max_fields :param = undef; 19 | field $max_field_length :param = undef; 20 | field $dropped_fields :reader = 0; 21 | field $data = {}; 22 | 23 | ADJUSTPARAMS ($params) { 24 | $self->set( %{ delete $params->{data} // {} } ); 25 | } 26 | 27 | method $validate_attribute_value ( $value ) { 28 | # Attribute values cannot be undefined but logging this is noisy 29 | return unless defined $value; 30 | 31 | if ( is_arrayref $value ) { 32 | if ( any { ref } @$value ) { 33 | $logger->trace('Attribute values that are lists cannot themselves hold references'); 34 | return; 35 | } 36 | 37 | # Make sure we do not store the same reference that was 38 | # passed as a value, since the list on the other side of 39 | # that reference can be modified without going through 40 | # our checks 41 | $value = $max_field_length ? [ 42 | map { 43 | defined ? substr( $_, 0, $max_field_length ) : $_ 44 | } @$value 45 | ] : [ @$value ]; 46 | } 47 | elsif ( ref $value ) { 48 | $logger->trace('Attribute values cannot be references'); 49 | return; 50 | } 51 | elsif ( $max_field_length ) { 52 | $value = substr $value, 0, $max_field_length; 53 | } 54 | 55 | ( 1, $value ); 56 | } 57 | 58 | method set ( %args ) { 59 | my $recorded = 0; 60 | for ( pairs %args ) { 61 | my ( $key, $value ) = @$_; 62 | 63 | $key ||= do { 64 | $logger->debugf("Attribute names should not be empty. Setting to 'null' instead"); 65 | 'null'; 66 | }; 67 | 68 | my $fields = scalar %$data; 69 | $fields++ unless exists $data->{$key}; 70 | 71 | next if $max_fields && $fields > $max_fields; 72 | 73 | my $ok; 74 | ( $ok, $value ) = $self->$validate_attribute_value($value); 75 | 76 | next unless $ok; 77 | 78 | $recorded++; 79 | $data->{$key} = $value; 80 | } 81 | 82 | my $dropped = +( keys %args ) - $recorded; 83 | 84 | $logger->debugf( 85 | 'Dropped %s attribute entr%s because %s invalid%s', 86 | $dropped, 87 | $dropped > 1 ? ( 'ies', 'they were' ) : ( 'y', 'it was' ), 88 | $max_fields 89 | ? " or would have exceeded field limit ($max_fields)" : '', 90 | ) if $logger->is_debug && $dropped > 0; 91 | 92 | $dropped_fields += $dropped; 93 | 94 | return $self; 95 | } 96 | 97 | method to_hash () { 98 | dclone $data; 99 | } 100 | } 101 | 102 | role OpenTelemetry::Attributes { 103 | field $attributes; 104 | 105 | ADJUSTPARAMS ( $params ) { 106 | $attributes = OpenTelemetry::AttributeMap->new( 107 | data => delete $params->{attributes} // {}, 108 | max_fields => delete $params->{attribute_count_limit}, 109 | max_field_length => delete $params->{attribute_length_limit}, 110 | ); 111 | } 112 | 113 | method dropped_attributes () { $attributes->dropped_fields } 114 | 115 | method attributes () { $attributes->to_hash } 116 | } 117 | -------------------------------------------------------------------------------- /lib/OpenTelemetry/Guides/Libraries.pod: -------------------------------------------------------------------------------- 1 | =encoding UTF-8 2 | 3 | =head1 NAME 4 | 5 | OpenTelemetry::Guides::Libraries - Using OpenTelemetry instrumentation libraries 6 | 7 | =head1 DESCRIPTION 8 | 9 | This page provides some detail around instrumentation libraries, and how to 10 | use them. These are distributions that modify modules from other CPAN 11 | distributions so they generate telemetry data. 12 | 13 | If you are interested in instrumenting your application code so it generates 14 | telemetry data, see instead L. For 15 | details on how to send that data to other backends for processing, refer to 16 | L. 17 | 18 | =head1 INSTRUMENTATION LIBRARIES 19 | 20 | When you develop an app, you might use third-party libraries and frameworks 21 | to accelerate your work. If you then instrument your app using OpenTelemetry, 22 | you might want to avoid spending additional time to manually add traces, logs, 23 | and metrics to the third-party libraries and frameworks you use. 24 | 25 | Many libraries and frameworks already support OpenTelemetry or are supported 26 | through OpenTelemetry instrumentation, so that they can generate telemetry 27 | you can export to an observability back end. 28 | 29 | If you are instrumenting an app or service that uses third-party libraries or 30 | frameworks, follow these instructions to learn how to use natively 31 | instrumented libraries and instrumentation libraries for your dependencies. 32 | 33 | =head2 Use instrumentation libraries 34 | 35 | If you are using a library does not generate its own OpenTelemetry data, you 36 | can use 37 | L 38 | to make it generate telemetry. For example, if you are using L and 39 | enable L, any uses of that library 40 | either from code you write or code you've imported, will automatically 41 | generate telemetry data for any HTTP request. 42 | 43 | The easiest way to manage instrumentation libraries is with the 44 | L module, which allows you to load, configure, 45 | and unload any instrumentation you have available in your system. 46 | 47 | The simplest way to use it is to enable an instrumentation by name: 48 | 49 | use OpenTelemetry::Instrumentation qw( HTTP::Tiny DBI ); 50 | 51 | Alternatively, you can use it to load all of the available instrumentations for 52 | any library you have already loaded: 53 | 54 | use OpenTelemetry::Instrumentation -all; 55 | 56 | =head2 Configuring specific instrumentation libraries 57 | 58 | When you load an instrumentation by name using L, 59 | you can pass an optional hash reference with options for that instrumentation 60 | (refer to that instrumentation's documentation to see what options they 61 | support, if any): 62 | 63 | use OpenTelemetry::Instrumentation ( 64 | HTTP::Tiny => { 65 | request_headers => ['x-spline-reticulation'], 66 | response_headers => ['x-reticulated'], 67 | }, 68 | 'DBI', 69 | ); 70 | 71 | =head1 WHAT NEXT? 72 | 73 | This document described how to generate telemetry data for code you imported 74 | into your application. If you want to instrument your own code, you should look 75 | at L. 76 | 77 | Once you have the data, you'll also want to configure an appropriate exporter to 78 | send that data to one or more telemetry backends. For that, see 79 | L. 80 | 81 | =head1 COPYRIGHT AND LICENSE 82 | 83 | This document is copyright (c) 2024 by José Joaquín Atria. 84 | 85 | It is based on the original OpenTelemetry documentation for Ruby which is 86 | (c) OpenTelemetry Authors and available at 87 | L. It has been modified to 88 | fit the Perl implementation. 89 | 90 | This is free software; you can redistribute it and/or modify it under the same 91 | terms as the Perl 5 programming language system itself. 92 | -------------------------------------------------------------------------------- /lib/OpenTelemetry/Instrumentation/DBI.pm: -------------------------------------------------------------------------------- 1 | package OpenTelemetry::Instrumentation::DBI; 2 | # ABSTRACT: OpenTelemetry instrumentation for DBI 3 | 4 | our $VERSION = '0.034'; 5 | 6 | use strict; 7 | use warnings; 8 | use experimental 'signatures'; 9 | use feature 'state'; 10 | 11 | use Class::Inspector; 12 | use Class::Method::Modifiers 'install_modifier'; 13 | use Feature::Compat::Try; 14 | use OpenTelemetry::Constants qw( SPAN_KIND_CLIENT SPAN_STATUS_ERROR SPAN_STATUS_OK ); 15 | use OpenTelemetry::Context; 16 | use OpenTelemetry::Trace; 17 | use OpenTelemetry; 18 | use Syntax::Keyword::Dynamically; 19 | 20 | use parent 'OpenTelemetry::Instrumentation'; 21 | 22 | sub dependencies { 'DBI' } 23 | 24 | my ( $EXECUTE, $DO, $loaded ); 25 | sub uninstall ( $class ) { 26 | return unless $loaded; 27 | no strict 'refs'; 28 | no warnings 'redefine'; 29 | delete $Class::Method::Modifiers::MODIFIER_CACHE{'DBI::st'}{execute}; 30 | *{'DBI::st::execute'} = $EXECUTE; 31 | *{'DBI::db::do'} = $DO; 32 | undef $loaded; 33 | return; 34 | } 35 | 36 | sub install ( $class, %options ) { 37 | return if $loaded; 38 | return unless Class::Inspector->loaded('DBI'); 39 | 40 | my $wrapper = sub ( $dbh, $statement, $orig, $handle, @args ) { 41 | state %meta; 42 | 43 | my $name = $dbh->{Name}; 44 | 45 | my $info = $meta{$name} //= do { 46 | my %meta = ( 47 | 'db.system' => lc $dbh->{Driver}{Name}, 48 | ); 49 | 50 | $meta{'db.user'} = $dbh->{Username} if $dbh->{Username}; 51 | $meta{'server.address'} = $1 if $name =~ /host=([^;]+)/; 52 | $meta{'server.port'} = $1 if $name =~ /port=([0-9]+)/; 53 | 54 | # Driver-specific metadata available before call 55 | if ( $meta{'db.system'} eq 'mysql' ) { 56 | $meta{'network.transport'} = 'IP.TCP'; 57 | } 58 | 59 | \%meta; 60 | }; 61 | 62 | $statement = $statement =~ s/^\s+|\s+$//gr =~ s/\s+/ /gr; 63 | 64 | my $span = OpenTelemetry->tracer_provider->tracer->create_span( 65 | name => substr($statement, 0, 100) =~ s/\s+$//r, 66 | kind => SPAN_KIND_CLIENT, 67 | attributes => { 68 | 'db.connection_string' => $name, 69 | 'db.statement' => $statement, 70 | %$info, 71 | }, 72 | ); 73 | 74 | dynamically OpenTelemetry::Context->current 75 | = OpenTelemetry::Trace->context_with_span($span); 76 | 77 | try { 78 | return $handle->$orig(@args); 79 | } 80 | catch ( $error ) { 81 | my ($description) = split /\n/, $error =~ s/^\s+|\s+$//gr, 2; 82 | $description =~ s/ at \S+ line \d+\.$//a; 83 | 84 | $span->record_exception($error); 85 | $span->set_status( SPAN_STATUS_ERROR, $description ); 86 | 87 | die $error; 88 | } 89 | finally { 90 | if ( $handle->err ) { 91 | my $error = $handle->errstr =~ s/^\s+|\s+$//gr; 92 | 93 | my ($description) = split /\n/, $error, 2; 94 | $description =~ s/ at \S+ line \d+\.$//a; 95 | 96 | $span->set_status( SPAN_STATUS_ERROR, $description ); 97 | } 98 | else { 99 | $span->set_status( SPAN_STATUS_OK ); 100 | } 101 | 102 | $span->end; 103 | } 104 | }; 105 | 106 | $EXECUTE = \&DBI::st::execute; 107 | install_modifier 'DBI::st' => around => execute => sub { 108 | my ( undef, $sth ) = @_; 109 | unshift @_, $sth->{Database}, $sth->{Statement}; 110 | goto $wrapper; 111 | }; 112 | 113 | $DO = \&DBI::st::do; 114 | install_modifier 'DBI::db' => around => do => sub { 115 | my ( undef, $dbh, $sql ) = @_; 116 | unshift @_, $dbh, $sql; 117 | goto $wrapper; 118 | }; 119 | 120 | return $loaded = 1; 121 | } 122 | 123 | 1; 124 | -------------------------------------------------------------------------------- /t/OpenTelemetry/Propagator/Baggage.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use Test2::V0 -target => 'OpenTelemetry::Propagator::Baggage'; 4 | use Test2::Tools::OpenTelemetry; 5 | 6 | use OpenTelemetry::Context; 7 | use OpenTelemetry::Baggage; 8 | 9 | my $carrier = {}; 10 | my $propagator = CLASS->new; 11 | 12 | is my $KEY = $propagator->keys, 'baggage', 'Can read propagator keys'; 13 | 14 | # Extracting baggage from a carrier that has had 15 | # no baggage injected into it returns the current context 16 | # or whatever context has been provided 17 | subtest 'Extract without baggage' => sub { 18 | ref_is my $a = $propagator->extract($carrier), 19 | OpenTelemetry::Context->current, 20 | 'Returns current context if none provided'; 21 | 22 | ref_is $propagator->extract($carrier, undef), 23 | OpenTelemetry::Context->current, 24 | 'Returns current context if undef provided'; 25 | 26 | my $key = $a->key('x'); 27 | my $b = $a->set( $key, 123 ); 28 | 29 | ref_is $propagator->extract( $carrier, $b ) => $b, 30 | 'Returns provided context if not in carrier'; 31 | 32 | is $b->get($key), 123, 'Can read from defaulted context'; 33 | }; 34 | 35 | # Injecting baggage into a carrier if no baggage exists in the 36 | # provided context (or in the current context, if no context was 37 | # provided) leaves the carrier untouched 38 | subtest 'Inject without baggage' => sub { 39 | ref_is $propagator->inject($carrier), $propagator, 40 | 'Inject returns self with no context'; 41 | is $carrier, {}, 'Nothing injected'; 42 | 43 | ref_is $propagator->inject( $carrier, OpenTelemetry::Context->current ), 44 | $propagator, 45 | 'Inject returns self with context with no baggage'; 46 | 47 | ref_is $propagator->inject( $carrier, undef ), 48 | $propagator, 49 | 'Inject returns self with undef context'; 50 | 51 | is $carrier, {}, 'Nothing injected'; 52 | }; 53 | 54 | # If there is baggage in the context, this is injected into the 55 | # carrier. If no context is provided, then the baggage is read from 56 | # the current context 57 | subtest 'Inject with baggage' => sub { 58 | my $context = OpenTelemetry::Baggage->set( foo => 123, 'META' ); 59 | 60 | ref_is $propagator->inject( $carrier, $context ), $propagator, 61 | 'Inject returns self'; 62 | 63 | is $carrier, { $KEY => 'foo=123;META' }, 'Baggage injected into carrier'; 64 | }; 65 | 66 | # If the carrier has had baggage injected into it, then extract will 67 | # return a context that contains it. We can read the baggage from the 68 | # context, which returns an object representing the read entry 69 | subtest 'Extract with baggage' => sub { 70 | is my $ctx = $propagator->extract($carrier), 71 | object { prop isa => 'OpenTelemetry::Context' }, 72 | 'Extract returns context'; 73 | 74 | is + OpenTelemetry::Baggage->get( 'foo', $ctx ), object { 75 | prop isa => 'OpenTelemetry::Baggage::Entry'; 76 | call value => '123'; 77 | call meta => 'META'; 78 | }, 'Baggage can read injected key from extracted context'; 79 | }; 80 | 81 | subtest 'Exceptions from callbacks' => sub { 82 | subtest Inject => sub { 83 | my $context = OpenTelemetry::Baggage->set( foo => 123, 'META' ); 84 | 85 | is messages { 86 | ref_is $propagator->inject( {}, $context, sub { die 'boom' } ), 87 | $propagator, 'Returns self'; 88 | } => [ 89 | [ error => OpenTelemetry => match qr/Error while injecting .* boom/ ], 90 | ], 'Logs error from callback'; 91 | }; 92 | 93 | subtest Extract => sub { 94 | is messages { 95 | my $context = OpenTelemetry::Context->new; 96 | my $carrier = { $KEY => 'foo=123;META' }; 97 | 98 | ref_is $propagator->extract( $carrier, $context, sub { die 'boom' } ), 99 | $context, 'Returns provided context'; 100 | } => [ 101 | [ error => OpenTelemetry => match qr/Error while extracting .* boom/ ], 102 | ], 'Logs error from callback'; 103 | }; 104 | }; 105 | 106 | done_testing; 107 | -------------------------------------------------------------------------------- /lib/OpenTelemetry/Instrumentation.pm: -------------------------------------------------------------------------------- 1 | package OpenTelemetry::Instrumentation; 2 | # ABSTRACT: Top-level interface for OpenTelemetry instrumentations 3 | 4 | our $VERSION = '0.034'; 5 | 6 | use strict; 7 | use warnings; 8 | use experimental 'signatures'; 9 | 10 | use Feature::Compat::Try; 11 | use List::Util 'uniqstr'; 12 | use Module::Runtime (); 13 | use Module::Pluggable search_path => [qw( 14 | OpenTelemetry::Instrumentation 15 | OpenTelemetry::Integration 16 | )]; 17 | use Scalar::Util 'blessed'; 18 | use Ref::Util qw( is_coderef is_hashref is_arrayref ); 19 | use OpenTelemetry::Common (); 20 | 21 | my $logger = OpenTelemetry::Common::internal_logger; 22 | 23 | # To be overriden by instrumentations 24 | sub dependencies { } 25 | sub uninstall { } # experimental 26 | 27 | my %REGISTRY; 28 | my sub find_instrumentations { 29 | my $class = shift; 30 | return if %REGISTRY; # Runs once and caches results 31 | 32 | # Inlined from OpenTelemetry::Common to read Perl-specific config 33 | my $legacy_support = $ENV{OTEL_PERL_USE_LEGACY_INSTRUMENTATIONS} // 1; 34 | $legacy_support 35 | = $legacy_support =~ /^true$/i ? 1 36 | : $legacy_support =~ /^false$/i ? 0 37 | : $legacy_support; 38 | 39 | # We sort the plugins so that we prefer the Instrumentation namespace 40 | for ( sort $class->plugins ) { 41 | last if /^OpenTelemetry::Integration::/ && !$legacy_support; 42 | $REGISTRY{ s/^OpenTelemetry::(?:Instrumentation|Integration):://r } ||= $_ 43 | } 44 | 45 | return; 46 | } 47 | 48 | sub for_package ($class, $package, @) { 49 | find_instrumentations($class); 50 | $REGISTRY{$package // ''}; 51 | } 52 | 53 | my @installed; 54 | sub import ( $class, @args ) { 55 | return unless @args; 56 | 57 | my $all = $args[0] =~ /^[:-]all$/ && shift @args; 58 | 59 | my %configuration; 60 | while ( my $key = shift @args ) { 61 | my $options = is_hashref($args[0]) || is_arrayref($args[0]) 62 | ? shift @args : {}; 63 | 64 | # Legacy namespace support. If we are loading an integration 65 | # by name which does not exist in INC in the new namespace, 66 | # but does exist in the legacy namespace, we use the legacy 67 | # name instead. 68 | my $instrumentation = $class->for_package($key); 69 | 70 | unless ( $instrumentation ) { 71 | $logger->warn( 72 | "Unable to load OpenTelemetry instrumentation for $key: Can't locate any suitable module in \@INC (you may need to install OpenTelemetry::Instrumentation::$key) (\@INC entries checked: @INC)", 73 | ); 74 | next; 75 | } 76 | 77 | $configuration{$instrumentation} = $options; 78 | } 79 | 80 | if ($all) { 81 | find_instrumentations($class); 82 | $configuration{$_} //= {} for values %REGISTRY; 83 | } 84 | 85 | for my $package ( keys %configuration ) { 86 | try { 87 | $logger->tracef('Loading %s', $package); 88 | 89 | Module::Runtime::require_module($package); 90 | 91 | # We only load dependencies if we are not loading every module 92 | unless ($all) { 93 | Module::Runtime::require_module($_) for $package->dependencies; 94 | } 95 | 96 | my $config = $configuration{ $package }; 97 | my $ok = $package->install( is_hashref $config ? %$config : @$config ); 98 | 99 | if ($ok) { 100 | push @installed, $package; 101 | } 102 | else { 103 | $logger->tracef("$package did not install itself"); 104 | } 105 | 106 | } 107 | catch ($e) { 108 | # Just a warning, if we're loading everything then 109 | # we shouldn't cause chaos just because something 110 | # doesn't happen to be available. 111 | $logger->warnf('Unable to load %s: %s', $package, $e); 112 | } 113 | } 114 | } 115 | 116 | sub unimport ( $class, @args ) { 117 | @args = @installed unless @args; 118 | $_->uninstall for @args; 119 | return; 120 | } 121 | 122 | 1; 123 | -------------------------------------------------------------------------------- /lib/OpenTelemetry/Propagator/TraceContext/TraceParent.pod: -------------------------------------------------------------------------------- 1 | =encoding UTF-8 2 | 3 | =head1 NAME 4 | 5 | OpenTelemetry::Propagator::TraceContext::TraceParent - Represents a TraceParent in a W3C TraceContext 6 | 7 | =head1 SYNOPSIS 8 | 9 | use OpenTelemetry; 10 | use OpenTelemetry::Propagator::TraceContext::TraceParent; 11 | 12 | my $span = OpenTelemetry->span_from_context; 13 | my $traceparent = OpenTelemetry::Propagator::TraceContext::TraceParent 14 | ->from_span_context( $span->context ); 15 | 16 | my $roundtrip = OpenTelemetry::Propagator::TraceContext::TraceParent 17 | ->from_string( $traceparent->to_string ); 18 | 19 | =head1 DESCRIPTION 20 | 21 | This class can be used to represent the trace parent as defined in a 22 | L. It contains methods 23 | to parse C header strings, as well as to generate them. 24 | 25 | =head1 METHODS 26 | 27 | =head2 new 28 | 29 | $new = OpenTelemetry::Propagator::TraceContext::TraceParent->new( 30 | trace_id => $trace_id, 31 | span_id => $span_id, 32 | trace_flags => $trace_flags, 33 | version => $version // 0, 34 | ); 35 | 36 | Constructs a new trace parent from scratch. Takes the following named parameters: 37 | 38 | =over 39 | 40 | =item span_id 41 | 42 | An 8-byte binary ID for the span this span context should be connected to. 43 | Defaults to a new random trace ID as returned by 44 | L. 45 | 46 | =item trace_id 47 | 48 | A 16-byte binary ID for the trace this span context should be connected to. 49 | Defaults to a new random trace ID as returned by 50 | L. 51 | 52 | =item trace_flags 53 | 54 | An instance of L with 55 | details about the trace. See that module's documentation for more details. 56 | Defaults to an empty set of flags. 57 | =over 58 | 59 | =item version 60 | 61 | A numeric version identifier. Defaults to 0, which at the moment of writing 62 | is the only version in existence. 63 | 64 | =back 65 | 66 | This raw constructor is mostly of used internally. Most commonly, a trace 67 | parent will be constructed either from the data in a 68 | L with the L, or from 69 | a header string with L. Both of these constructors are described 70 | inn more detail below. 71 | 72 | =head2 from_span_context 73 | 74 | $new = OpenTelemetry::Propagator::TraceContext::TraceParent->from_span_context( 75 | $span_context, 76 | ); 77 | 78 | Takes a positional L and constructs a new 79 | trace parent from the data it contains. 80 | 81 | =head2 from_string 82 | 83 | $new = OpenTelemetry::Propagator::TraceContext::TraceParent->from_string( 84 | $header_string 85 | ); 86 | 87 | Takes a positional string, such as the one that could be found in a 88 | C header. This method will parse the string and return the 89 | trace parent that string represents. 90 | 91 | See L below for a way to generate this string. 92 | 93 | =head2 to_string 94 | 95 | $string = $traceparent->to_string; 96 | 97 | Returns the trace parent stringified as defined in the 98 | L. This string is 99 | suitable to be used as the value eg. in a header for further propagation. 100 | 101 | Passing this string to L will return an equivalent trace parent. 102 | 103 | =head1 SEE ALSO 104 | 105 | =over 106 | 107 | =item L 108 | 109 | =item L 110 | 111 | =item L 112 | 113 | =item L 114 | 115 | =item L 116 | 117 | =back 118 | 119 | =head1 COPYRIGHT AND LICENSE 120 | 121 | This software is copyright (c) 2023 by José Joaquín Atria. 122 | 123 | This is free software; you can redistribute it and/or modify it under the same 124 | terms as the Perl 5 programming language system itself. 125 | -------------------------------------------------------------------------------- /lib/OpenTelemetry/Guides/Exporters.pod: -------------------------------------------------------------------------------- 1 | =encoding UTF-8 2 | 3 | =head1 NAME 4 | 5 | OpenTelemetry::Guides::Exporters - Process and export your OpenTelemetry data 6 | 7 | =head1 DESCRIPTION 8 | 9 | This page talks about the tools and processes used for sending telemetry data 10 | to OpenTelemetry-enabled backends. For information on how to generate that 11 | data, from your own code or your dependencies, see L and 12 | L respectively. 13 | 14 | =head1 EXPORTING TELEMETRY 15 | 16 | Send telemetry to the OpenTelemetry Collector to make sure it's exported 17 | correctly. Using the Collector in production environments is a best practice. 18 | To visualise your telemetry, export it to a backend such as 19 | L, 20 | L, 21 | L, 22 | or a L backend. 23 | 24 | =head2 OTLP 25 | 26 | To send trace data to a OTLP endpoint (like the 27 | L or 28 | L) you'll want to use an exporter package, 29 | such as L and configure it to send that data 30 | wherever it needs to go. 31 | 32 | Let's use the example from L. In that case, 33 | we got the traces exported to STDOUT because we executed it with the 34 | C environment variable set to C. But we can 35 | change that. 36 | 37 | We'll need to install some additional dependencies. We'll need the exporter 38 | itself, and since this example is running on L we will also need 39 | to install L so the batch span processor used by the 40 | OTLP exporter hooks into the Mojolicious event loop. 41 | 42 | cpanm \ 43 | OpenTelemetry::Exporter::OTLP \ 44 | IO::Async::Loop::Mojo 45 | 46 | Before we execute the code, we'll also need to set up something that can 47 | receive the OTLP traffic generated by the exporter. For this, we can use 48 | L 49 | in this distribution which has a "docker compose" stack with an OpenTelemetry 50 | Collector that prints any telemetry it receives to the console, and is 51 | connected to a Jaeger and a Prometheus instance: 52 | 53 | git clone https://github.com/jjatria/perl-opentelemetry 54 | cd perl-opentelemetry/examples/collector 55 | docker compose up 56 | 57 | By default traces are sent to an OTLP endpoint listening on localhost:4318. 58 | You can change the endpoint by setting the 59 | L 60 | environment variable accordingly (we can use the default value). We'll also 61 | set a L so the 62 | exporter knows the source of the traces, and set 63 | L 64 | to 1 so we export every request to the collector as it comes (note that you 65 | probably don't want to do this in production). Finally, we'll set 66 | L to C to specify which event loop 67 | implementation to use: 68 | 69 | OTEL_SERVICE_NAME=dice \ 70 | OTEL_BSP_MAX_EXPORT_BATCH_SIZE=1 \ 71 | IO_ASYNC_LOOP=Mojo \ 72 | ./Dice daemon 73 | 74 | Making requests to L should now generate trace 75 | data that you can examine by going to L. 76 | 77 | =head1 WHAT NEXT? 78 | 79 | This document described how to export telemetry data generated by your 80 | application to a backend for further processing. If you want to read about 81 | generating that data from your own code you should read 82 | L, or 83 | L if you want to generate it from a library 84 | on CPAN. 85 | 86 | =head1 COPYRIGHT AND LICENSE 87 | 88 | This document is copyright (c) 2024 by José Joaquín Atria. 89 | 90 | It is based on the original OpenTelemetry documentation for Ruby which is 91 | (c) OpenTelemetry Authors and available at 92 | L. It has been modified to 93 | fit the Perl implementation. 94 | 95 | This is free software; you can redistribute it and/or modify it under the same 96 | terms as the Perl 5 programming language system itself. 97 | -------------------------------------------------------------------------------- /lib/OpenTelemetry/Trace/Tracer.pod: -------------------------------------------------------------------------------- 1 | =encoding UTF-8 2 | 3 | =head1 NAME 4 | 5 | OpenTelemetry::Trace::Tracer - A span factory for OpenTelemetry 6 | 7 | =head1 SYNOPSIS 8 | 9 | use OpenTelemetry; 10 | 11 | my $provider = OpenTelemetry->tracer_provider; 12 | my $tracer = $provider->tracer; 13 | 14 | # Create a span for manual use 15 | my $span = $tracer->create_span(%args); 16 | 17 | # Or execute code within a span (experimental) 18 | $tracer->in_span( my_span => sub ( $span, $context ) { 19 | ... 20 | }); 21 | 22 | =head1 DESCRIPTION 23 | 24 | A tracer is responsible for creating L objects. 25 | 26 | =head1 METHODS 27 | 28 | =head2 create_span 29 | 30 | $span = $tracer->create_span( 31 | attributes => $attributes // {}, 32 | kind => $span_kind // $internal, 33 | links => $links // [], 34 | name => $name, // 'empty', 35 | parent => $context // undef, 36 | start => $timestamp // time, 37 | ) 38 | 39 | Creates an L instance associated with this trace. 40 | 41 | Takes a list of key / value pairs. Of these, the only one that callers are 42 | I expected to provide is the span name: not doing so may result in 43 | a warning being logged and a placeholder name will be used instead. All other 44 | keys are optional and can be used to further specify the span. 45 | 46 | Note that even though the majority of these can be set after a span's 47 | creation, it's always recommended to set these on creation if possible, since 48 | this is the only time when they are guaranteed to be available for any 49 | behaviours that may depend on them (eg. span sampling). 50 | 51 | Spans can have zero or more child spans, which represent causally related 52 | operations. These can be created by passing a 53 | L that holds the parent span as the value for the 54 | C parameter. If no parent is set, or if the provided context does not 55 | contain a span, the created span is considered a I. Typically, a 56 | trace will only have one root span. 57 | 58 | Spans can also have links to other spans, including those that belong to a 59 | different trace. These can be set on span creation via the C parameter, 60 | which must hold a reference to an array of hashes. The hashes should have a 61 | C key set to the L of the span to 62 | link to, and an optional C key set to a hashref of attributes 63 | to set on the link. 64 | 65 | It is the responsibility of the user to make sure that every span that has 66 | been created is ended (via a call to its L 67 | method). 68 | 69 | =head2 in_span 70 | 71 | # Experimental 72 | $return = $tracer->in_span( 73 | $span_name, 74 | %span_arguments, 75 | sub ( $span, $context ) { ...; $return }, 76 | ); 77 | 78 | This method is currently experimental. 79 | 80 | Takes a string as the first argument and a subroutine reference as the last 81 | argument, and executes the code in that reference within a span using the 82 | string as its name. The subroutine reference will receive the created span and 83 | the current context (containing the span) as arguments. The span is guaranteed 84 | to be ended after execution of the subroutine ends by any means. 85 | 86 | The status of the span will be set automatically at the end of the block 87 | either to an Ok status (if the subroutine ended without error) or to Error 88 | otherwise. In the latter case, the first line of the stringified error will be 89 | set as the description, after removing any code context data (ie. the " at 90 | script.pl line 123." that is added automatically by Perl). This can be avoided 91 | by manually ending the span if needed. 92 | 93 | Any additional parameters passed to this method between the span name and the 94 | code reference will be passed as-is to the call to L made when 95 | creating the span. Note that the name provided as the first argument is 96 | mandatory and will take precedence over any name set in these additional 97 | parameters. 98 | 99 | This method returns whatever the executed code reference returns. 100 | 101 | =head1 COPYRIGHT AND LICENSE 102 | 103 | This software is copyright (c) 2023 by José Joaquín Atria. 104 | 105 | This is free software; you can redistribute it and/or modify it under the same 106 | terms as the Perl 5 programming language system itself. 107 | -------------------------------------------------------------------------------- /lib/OpenTelemetry/Propagator.pod: -------------------------------------------------------------------------------- 1 | =encoding UTF-8 2 | 3 | =head1 NAME 4 | 5 | OpenTelemetry::Propagator - An abstract interface for OpenTelemetry propagators 6 | 7 | =head1 SYNOPSIS 8 | 9 | use Object::Pad; 10 | 11 | class My::Propagator :does(OpenTelemetry::Propagator) { 12 | method extract { ... } 13 | method inject { ... } 14 | method keys { ... } 15 | } 16 | 17 | my $propagator = My::Propagator->new; 18 | 19 | # Inject data from the context to a carrier 20 | my $carrier = {}; 21 | $propagator->inject( $carrier, $context ); 22 | 23 | # Extract data from a carrier to the context 24 | my $new_context = $propagator->extract( $carrier, $context ); 25 | 26 | # Reset the carrier for further operations 27 | delete @{$carrier}{ $propagator->keys }; 28 | 29 | =head1 DESCRIPTION 30 | 31 | This package provides an L role that defines the interface 32 | that OpenTelemetry propagator classes should implement. Propagators are 33 | objects that can interact with the context (which can be either an 34 | implicit or explicit instance of L) and inject or 35 | extract data using a variety of different formats. 36 | 37 | It is the responsibility of propagator classes implementing this interface 38 | to define those formats. 39 | 40 | =head1 METHODS 41 | 42 | Although there is unfortunately no way to currently enforce it, this document 43 | describes the way the methods of a class implementing this role are expected 44 | to behave. 45 | 46 | =head2 inject 47 | 48 | $propagator = $propagator->inject( 49 | $carrier, 50 | $context // OpenTelemetry::Context->current, 51 | $setter // OpenTelemetry::Propagator::TextMap::SETTER, 52 | ) 53 | 54 | Injects data from the context into a carrier. Must take a mandatory reference 55 | to a carrier data structure into which the data will be injected, and an 56 | optional instance of L from where the data will be 57 | read. If no context is provided, the current context must be used. 58 | 59 | To support different kinds of carriers, a setter subroutine reference can be 60 | passed as the third argument. This setter subroutine will be called with the 61 | carrier and a key / value pair, and is expected to store the value under the 62 | key in the carrier. If no setter is provided, classes implementing this 63 | interface should provide a default setter that is suitable for their use case. 64 | 65 | This method must return the calling propagator. 66 | 67 | =head2 extract 68 | 69 | $new_context = $propagator->extract( 70 | $carrier, 71 | $context // OpenTelemetry::Context->current, 72 | $getter // OpenTelemetry::Propagator::TextMap::GETTER, 73 | ) 74 | 75 | Extracts data from a carrier into the context. Must take a mandatory 76 | reference to a carrier data structure from where the data will be extracted, 77 | and an optional instance of L into which the data will 78 | be written. If no context is provided, the current context must be used. 79 | 80 | To support different kinds of carriers, a getter subroutine reference can be 81 | passed as the third argument. This getter will be called with the carrier and 82 | a string to be used as a key, and is expected to return the value stored under 83 | that key in the carrier. If no getter is provided, the default getter from 84 | L will be used, which assumes the carrier 85 | can be used as a hash reference. 86 | 87 | This method must return a new instance of L with the 88 | values from the provided context, and holding the data extracted from the 89 | carrier. 90 | 91 | =head2 keys 92 | 93 | @keys = $propagator->keys 94 | 95 | Returns the list of keys under which this propagator injects data into a 96 | carrier. If the carrier needs to be cleared, these are the keys that need to be 97 | deleted. 98 | 99 | =head1 SEE ALSO 100 | 101 | =over 102 | 103 | =item L 104 | 105 | =item L 106 | 107 | =item L 108 | 109 | =item L 110 | 111 | =item L 112 | 113 | =back 114 | 115 | =head1 COPYRIGHT AND LICENSE 116 | 117 | This software is copyright (c) 2023 by José Joaquín Atria. 118 | 119 | This is free software; you can redistribute it and/or modify it under the same 120 | terms as the Perl 5 programming language system itself. 121 | -------------------------------------------------------------------------------- /t/OpenTelemetry/Propagator/TraceContext/TraceState.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use Test2::V0 -target => 'OpenTelemetry::Propagator::TraceContext::TraceState'; 4 | use Test2::Tools::OpenTelemetry; 5 | 6 | subtest Parsing => sub { 7 | my $test = CLASS->from_string('rojo=00f067aa0ba902b7,congo=t61rcWkgMzE'); 8 | 9 | is $test, object { 10 | call [ get => 'rojo' ] => '00f067aa0ba902b7'; 11 | call [ get => 'congo' ] => 't61rcWkgMzE'; 12 | }, 'Can parse TraceState string'; 13 | 14 | is $test->to_string, 'rojo=00f067aa0ba902b7,congo=t61rcWkgMzE', 15 | 'Can generate TraceState string'; 16 | 17 | is CLASS->from_string(' foo=123 ,, bar=234 ')->to_string, 18 | 'foo=123,bar=234', 19 | 'Drops surrounding spaces and empty members'; 20 | 21 | is CLASS->from_string('foo=with spaces')->to_string, 22 | 'foo=with spaces', 23 | 'Keeps spaces inside values'; 24 | 25 | is CLASS->from_string(''), D, 'Can parse empty string'; 26 | is CLASS->from_string(undef), D, 'Ignores undefined string'; 27 | 28 | is CLASS->from_string('ok=1,this=or=that,!=123')->to_string, 'ok=1', 29 | 'Ignores bad keys and values'; 30 | 31 | is CLASS->from_string('test@cpan=timtowtdi'), object { 32 | call [ get => 'test@cpan' ] => 'timtowtdi'; 33 | }, 'Accepts multi-tenant keys'; 34 | }; 35 | 36 | subtest Modification => sub { 37 | my $test = CLASS->from_string('rojo=00f067aa0ba902b7,congo=t61rcWkgMzE'); 38 | 39 | is $test->set( new => 'deadbeef' )->to_string, 40 | 'new=deadbeef,rojo=00f067aa0ba902b7,congo=t61rcWkgMzE', 41 | 'Newly set members appear on left'; 42 | 43 | is $test->to_string, 44 | 'rojo=00f067aa0ba902b7,congo=t61rcWkgMzE', 45 | 'TraceState is immutable'; 46 | 47 | is $test->delete('congo')->to_string, 48 | 'rojo=00f067aa0ba902b7', 49 | 'Can delete members'; 50 | 51 | ref_is_not $test->delete('xxx'), $test, 52 | 'Deleting retusn new object even if missing'; 53 | 54 | is $test->delete('xxx')->to_string, 55 | 'rojo=00f067aa0ba902b7,congo=t61rcWkgMzE', 56 | 'Deleting missing field does nothing'; 57 | 58 | is $test->set( congo => 'deadbeef' )->to_string, 59 | 'congo=deadbeef,rojo=00f067aa0ba902b7', 60 | 'Setting an existing key overwrites'; 61 | 62 | is $test->get('congo'), 't61rcWkgMzE', 'Can read values'; 63 | is $test->get('xxx'), U, 'Reading missing values returns undefined'; 64 | 65 | subtest 'Invalid key / value' => sub { 66 | my $test = CLASS->from_string('foo=123'); 67 | 68 | is messages { 69 | is $test->set( '' => 123 )->to_string, 'foo=123', 'Ignore empty key'; 70 | } => [ 71 | [ debug => OpenTelemetry => match qr/^Invalid .* key: '' => '123'/ ], 72 | ], 'Log invalid key'; 73 | 74 | is messages { 75 | is $test->set( 'òó' => 1 )->to_string, 'foo=123', 'Ignore invalid key'; 76 | } => [ 77 | [ debug => OpenTelemetry => match qr/^Invalid .* key: 'òó' => '1'/ ], 78 | ], 'Log invalid key'; 79 | 80 | is messages { 81 | is $test->set( 'x' => "" )->to_string, 'foo=123', 'Ignore empty value'; 82 | } => [ 83 | [ debug => OpenTelemetry => match qr/^Invalid .* value: 'x' => ''/ ], 84 | ], 'Log invalid value'; 85 | 86 | is messages { 87 | is $test->set( 'x' => "," )->to_string, 'foo=123', 88 | 'Comma not a valid value'; 89 | } => [ 90 | [ debug => OpenTelemetry => match qr/^Invalid .* value: 'x' => ','/ ], 91 | ], 'Log invalid value'; 92 | 93 | is messages { 94 | is $test->set( 'x' => "\t" )->to_string, 'foo=123', 95 | 'Tab not a valid value'; 96 | } => [ 97 | [ debug => OpenTelemetry => match qr/^Invalid .* value: 'x' => '\t'/ ], 98 | ], 'Log invalid value'; 99 | 100 | is messages { 101 | is $test->set( 'x' => "\n" )->to_string, 'foo=123', 102 | 'Newline not a valid value'; 103 | } => [ 104 | [ debug => OpenTelemetry => match qr/^Invalid .* value: 'x' => '\n'/ ], 105 | ], 'Log invalid value'; 106 | 107 | is messages { 108 | is $test->set( 'x' => "\r" )->to_string, 'foo=123', 109 | 'Carriage return not a valid value'; 110 | } => [ 111 | [ debug => OpenTelemetry => match qr/^Invalid .* value: 'x' => '\r'/ ], 112 | ], 'Log invalid value'; 113 | }; 114 | }; 115 | 116 | done_testing; 117 | -------------------------------------------------------------------------------- /lib/OpenTelemetry/Trace.pod: -------------------------------------------------------------------------------- 1 | =encoding UTF-8 2 | 3 | =head1 NAME 4 | 5 | OpenTelemetry::Trace - Generic methods for the OpenTelemetry Tracing API 6 | 7 | =head1 SYNOPSIS 8 | 9 | use OpenTelemetry::Trace; 10 | 11 | # Retrieve a span from context 12 | my $span = OpenTelemetry::Trace->span_from_context; 13 | 14 | # Store a span in the context 15 | my $context = OpenTelemetry::Trace->context_with_span($span); 16 | 17 | # This is a no-op, since we are retrieving the span we stored 18 | $span = OpenTelemetry::Trace->span_from_context($context); 19 | 20 | =head1 DESCRIPTION 21 | 22 | This package provides some methods for injecting L objects into, and 23 | extracting them from, a given L. 24 | 25 | For the meat and bones of the OpenTelemetry Tracing API, please see the 26 | following packages: 27 | 28 | =over 29 | 30 | =item L, which provides access to 31 | L. 32 | 33 | =item L, which can create 34 | L. 35 | 36 | =item L, which allows the trace of a single 37 | operation. 38 | 39 | =back 40 | 41 | =head1 METHODS 42 | 43 | =head2 span_from_context 44 | 45 | $span = OpenTelemetry::Trace->span_from_context($context); 46 | 47 | Takes an optional L object, and returns the 48 | L object that has been stored within, if any. If 49 | no context was provided, the span will be read from the 50 | L. 51 | 52 | If no span was found in the context, this method returns an invalid span 53 | (see L below). 54 | 55 | =head2 context_with_span 56 | 57 | $context = OpenTelemetry::Trace->context_with_span( $span, $context ); 58 | 59 | Takes an L and returns a context that contains it 60 | (such that passing that context to eg. L would return the 61 | provided span). 62 | 63 | An optional L instance can be passed as a second 64 | argument, in which case it will be used as the base for the new context. If no 65 | context is provided, the L 66 | will be used. 67 | 68 | =head2 non_recording_span 69 | 70 | $span = OpenTelemetry::Trace->non_recording_span($span_context) 71 | 72 | Returns an instance of L that records no trace 73 | data. Operations on this span are effectively a no-op. 74 | 75 | Takes an instance of L to use as the 76 | context of the span. If none is provided, it will default to a new instance. 77 | 78 | =head2 generate_trace_id 79 | 80 | $id = OpenTelemetry::Trace->generate_trace_id; 81 | 82 | Generate a new random trace ID. This ID is guaranteed to be valid. 83 | 84 | =head2 generate_span_id 85 | 86 | $id = OpenTelemetry::Trace->generate_span_id; 87 | 88 | Generate a new random span ID. This ID is guaranteed to be valid. 89 | 90 | =head2 untraced_context 91 | 92 | $new_context = OpenTelemetry::Trace->untraced_context($context); 93 | 94 | Returns a new L instance which is marked as untraced. 95 | This can be used together with the L method below to 96 | locally disable tracing for internal operations: 97 | 98 | dynamically OpenTelemetry::Context->current 99 | = OpenTelemetry::Trace->untraced_context; 100 | 101 | This method takes an optional L to use as the base 102 | context. If none is provided, the 103 | L will be used. 104 | 105 | =head2 is_untraced_context 106 | 107 | $bool = OpenTelemetry::Trace->is_untraced_context($context); 108 | 109 | Takes an L instance and checks if it is marked as an 110 | untraced context (see the L method above). If no context is 111 | provided, the L will be used. 112 | 113 | Returns true if this is an untraced context, or false otherwise. 114 | 115 | =head1 SEE ALSO 116 | 117 | =over 118 | 119 | =item L 120 | 121 | =item L 122 | 123 | =item L 124 | 125 | =back 126 | 127 | =head1 COPYRIGHT AND LICENSE 128 | 129 | This software is copyright (c) 2023 by José Joaquín Atria. 130 | 131 | This is free software; you can redistribute it and/or modify it under the same 132 | terms as the Perl 5 programming language system itself. 133 | -------------------------------------------------------------------------------- /lib/OpenTelemetry/Trace/Span/Processor.pod: -------------------------------------------------------------------------------- 1 | =encoding UTF-8 2 | 3 | =head1 NAME 4 | 5 | OpenTelemetry::Trace::Span::Processor - Abstract interface of an OpenTelemetry span processor 6 | 7 | =head1 SYNOPSIS 8 | 9 | use Object::Pad; 10 | use Future::AsyncAwait; 11 | 12 | class My::Processor :does(OpenTelemetry::Trace::Span::Processor) { 13 | method on_start ( $span, $context ) { ... } 14 | method on_end ( $span ) { ... } 15 | 16 | async method shutdown ( $timeout // undef ) { ... } 17 | async method force_flush ( $timeout // undef ) { ... } 18 | } 19 | 20 | # Create it 21 | my $processor = My::Processor->new( ... ); 22 | 23 | # Register it with the OpenTelemetry tracer provider 24 | OpenTelemetry->tracer_provider->add_span_processor($processor); 25 | 26 | =head1 DESCRIPTION 27 | 28 | This module provides an abstract role that can be used by classes implementing 29 | OpenTelemetry span processors. Span processors are objects that are registered 30 | with a L (or, more accurately, with one 31 | of its subclasses such as those provided by an OpenTelemetry SDK) to perform 32 | additional operations on spans when they start and end. 33 | 34 | The processor is considered to be the start of a pipeline, which must be 35 | allowed to end with a L to export the telemetry data 36 | to some collector. To this end, processors are expected to accept an optional 37 | C parameter to their constructor. But processors can be used for 38 | other ends. 39 | 40 | =head1 METHODS 41 | 42 | =head2 new 43 | 44 | $processor = Class::Implementing::This::Role->new( 45 | exporter => ..., # optional 46 | ... 47 | ); 48 | 49 | Should take an optional exporter set to an instance of a class implementing 50 | the L role. Processor classes are free to accept any 51 | other parameters they choose. 52 | 53 | =head2 on_start 54 | 55 | $processor->on_start( $span, $parent_context ); 56 | 57 | Called when the span is started. It takes the newly created span object, and 58 | the L that holds the span's parent span, if any. 59 | This is the same value that was passed as the C paremeter to 60 | L. 61 | 62 | If the span that was created was a root span (a span without a parent), this 63 | value may be undefined, or it may be a L that does not 64 | hold a span. 65 | 66 | This method is called synchronously when the span was created, so it should 67 | not block or die. 68 | 69 | The return value of this method is ignored. 70 | 71 | =head2 on_end 72 | 73 | $processor->on_end( $span ); 74 | 75 | Called as soon as possible after the span has ended (so the end timestamp is 76 | already known). It takes the span that has just ended as its only parameter. 77 | 78 | This method is called synchronously when the span was created, so it should 79 | not block or die. 80 | 81 | The return value of this method is ignored. 82 | 83 | =head2 shutdown 84 | 85 | $result = await $processor->shutdown( $timeout // undef ); 86 | 87 | Takes an optional timeout value and returns a L that will be done 88 | when this span processor has completed shutting down. The shutdown process must 89 | include the effects of L, described below. After shutting down, 90 | the processor is not expected to do any further work, and should ignore any 91 | subsequent calls. 92 | 93 | The value of the future will be one of the 94 | L. 95 | 96 | =head2 force_flush 97 | 98 | $result = await $processor->force_flush( $timeout // undef ); 99 | 100 | Takes an optional timeout value and returns a L that will be done 101 | when this span processor has finished flushing. Flushing signals to the span 102 | processor that it should process the data for any unprocessed spans as soon as 103 | possible. This could be due to an imminent shutdown, but does not have to be. 104 | 105 | The value of the future will be one of the 106 | L. 107 | 108 | =head1 SEE ALSO 109 | 110 | =over 111 | 112 | =item L 113 | 114 | =item L 115 | 116 | =item L 117 | 118 | =item L 119 | 120 | =item L 121 | 122 | =item L 123 | 124 | =back 125 | 126 | =head1 COPYRIGHT AND LICENSE 127 | 128 | This software is copyright (c) 2023 by José Joaquín Atria. 129 | 130 | This is free software; you can redistribute it and/or modify it under the same 131 | terms as the Perl 5 programming language system itself. 132 | --------------------------------------------------------------------------------