Skip to content

Commit

Permalink
Rename integration namespace to instrumentation
Browse files Browse the repository at this point in the history
Writing the guides in the OpenTelemetry::Guides namespace revealed
that while the specification _always_ refers to "instrumentation
libraries", this distribution _always_ used the term "integration"
instead.

This (breaking) change renames the ::Integration namespace to more
closely align with the specification, hopefuly reducing any
confusion that might arise there. Since some code might already be
relying on that legacy namespace, this change makes efforts to
continue to support the legacy namespace, although this will likely
be removed in the future.
  • Loading branch information
jjatria committed Dec 15, 2024
1 parent 7d2233a commit 5b0ceb0
Show file tree
Hide file tree
Showing 23 changed files with 356 additions and 271 deletions.
4 changes: 4 additions & 0 deletions Changes
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ Revision history for OpenTelemetry
authors. Examples used in those documents has also been added to
the examples directory in this distribution. In the future, this
namespace will expand to include more guides.
* Renamed OpenTelemetry::Integration namespace to
OpenTelemetry::Instrumentation to be consistent with the names in
the specification. The legacy namespace will continue to be
supported for now, but new implementations should not use it.

0.025 2024-10-20 13:50:19+01:00 Europe/London

Expand Down
20 changes: 12 additions & 8 deletions META.json
Original file line number Diff line number Diff line change
Expand Up @@ -123,20 +123,24 @@
"file" : "lib/OpenTelemetry/Exporter.pm",
"version" : "0.025"
},
"OpenTelemetry::Integration" : {
"file" : "lib/OpenTelemetry/Integration.pm",
"OpenTelemetry::Instrumentation" : {
"file" : "lib/OpenTelemetry/Instrumentation.pm",
"version" : "0.025"
},
"OpenTelemetry::Instrumentation::DBI" : {
"file" : "lib/OpenTelemetry/Instrumentation/DBI.pm",
"version" : "0.025"
},
"OpenTelemetry::Integration::DBI" : {
"file" : "lib/OpenTelemetry/Integration/DBI.pm",
"OpenTelemetry::Instrumentation::HTTP::Tiny" : {
"file" : "lib/OpenTelemetry/Instrumentation/HTTP/Tiny.pm",
"version" : "0.025"
},
"OpenTelemetry::Integration::HTTP::Tiny" : {
"file" : "lib/OpenTelemetry/Integration/HTTP/Tiny.pm",
"OpenTelemetry::Instrumentation::LWP::UserAgent" : {
"file" : "lib/OpenTelemetry/Instrumentation/LWP/UserAgent.pm",
"version" : "0.025"
},
"OpenTelemetry::Integration::LWP::UserAgent" : {
"file" : "lib/OpenTelemetry/Integration/LWP/UserAgent.pm",
"OpenTelemetry::Integration" : {
"file" : "lib/OpenTelemetry/Integration.pm",
"version" : "0.025"
},
"OpenTelemetry::Logs::LogRecord::Processor" : {
Expand Down
3 changes: 2 additions & 1 deletion cpanfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ requires 'Future::AsyncAwait';
requires 'List::Util', '1.45'; # For uniq
requires 'Log::Any';
requires 'Module::Pluggable';
requires 'Module::Runtime';
requires 'Mutex';
requires 'Object::Pad', '0.74'; # For //= field initialisers
requires 'Ref::Util';
Expand All @@ -19,7 +20,7 @@ requires 'UUID::URandom';
requires 'X::Tiny';

on test => sub {
requires 'Class::Inspector'; # For OpenTelemetry::Integration test
requires 'Class::Inspector'; # For OpenTelemetry::Instrumentation test
requires 'Metrics::Any';
requires 'Test2::V0';
};
10 changes: 5 additions & 5 deletions examples/integrations/lwp → examples/instrumentations/lwp
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ use warnings;
use feature 'say';

# This example shows the minimum mount of code needed to generate
# telemetry data through an integration, in this case using
# telemetry data through an instrumentation, in this case using
# LWP::UserAgent.

# While loading the integration is all that is needed to instrument
# While loading the instrumentation is all that is needed to instrument
# the client code, in order to see it work we need two more things:
#
# 1. Some configured set of propagators, so that we have something
Expand All @@ -31,9 +31,9 @@ use OpenTelemetry qw( otel_tracer_provider );
# headers since we are not going to add any baggage to the context.
use OpenTelemetry::SDK;

# We load the the integration for LWP::UserAgent, which will automatically
# import that module for us. This automatic loading only happens if we
# request the integration by name.
# We load the the instrumentation for LWP::UserAgent, which will
# automatically import that module for us. This automatic loading only
# happens if we request the instrumentation by name.
use OpenTelemetry::Integration 'LWP::UserAgent';

# By default we will send a request to an echo server, so we can examine
Expand Down
2 changes: 1 addition & 1 deletion lib/OpenTelemetry/Common.pod
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ This module contains functions that are useful throughout the OpenTelemetry
codebase, and help provide a consistent way to handle certain processes.

They will most likely only be of interest to authors of OpenTelemetry
libraries and integrations.
libraries and instrumentation.

=head1 FUNCTIONS

Expand Down
20 changes: 10 additions & 10 deletions lib/OpenTelemetry/Guides/Libraries.pod
Original file line number Diff line number Diff line change
Expand Up @@ -36,31 +36,31 @@ If you are using a library does not generate its own OpenTelemetry data, you
can use
L<instrumentation libraries|https://opentelemetry.io/docs/specs/otel/glossary/#instrumentation-library>
to make it generate telemetry. For example, if you are using L<HTTP::Tiny> and
enable L<OpenTelemetry::Integration::HTTP::Tiny>, any uses of that library
enable L<OpenTelemetry::Instrumentation::HTTP::Tiny>, any uses of that library
either from code you write or code you've imported, will automatically
generate telemetry data for any HTTP request.

The easiest way to manage integration libraries is with the
L<OpenTelemetry::Integration> module, which allows you to load, configure, and
unload any integrations you have available in your system.
The easiest way to manage instrumentation libraries is with the
L<OpenTelemetry::Instrumentation> module, which allows you to load, configure,
and unload any instrumentation you have available in your system.

The simplest way to use it is to enable an integrations by name:
The simplest way to use it is to enable an instrumentation by name:

use OpenTelemetry::Integration qw( HTTP::Tiny DBI );
use OpenTelemetry::Instrumentation qw( HTTP::Tiny DBI );

Alternatively, you can use it to load all of the available integrations for
Alternatively, you can use it to load all of the available instrumentations for
any library you have already loaded:

use OpenTelemetry::Integration -all;
use OpenTelemetry::Instrumentation -all;

=head2 Configuring specific instrumentation libraries

When you load an instrumentation by name using L<OpenTelemetry::Integration>,
When you load an instrumentation by name using L<OpenTelemetry::Instrumentation>,
you can pass an optional hash reference with options for that instrumentation
(refer to that instrumentation's documentation to see what options they
support, if any):

use OpenTelemetry::Integration (
use OpenTelemetry::Instrumentation (
HTTP::Tiny => {
request_headers => ['x-spline-reticulation'],
response_headers => ['x-reticulated'],
Expand Down
2 changes: 1 addition & 1 deletion lib/OpenTelemetry/Guides/Quickstart.pod
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ place during the C<BEGIN> phase, which happens before your application
starts up.

Modify your application to load the SDK and enable the Mojolicious
integration plugin:
instrumentation plugin:

#!/usr/bin/env perl
use Mojolicious::Lite -signatures;
Expand Down
115 changes: 115 additions & 0 deletions lib/OpenTelemetry/Instrumentation.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package OpenTelemetry::Instrumentation;
# ABSTRACT: Top-level interface for OpenTelemetry instrumentations

our $VERSION = '0.026';

use strict;
use warnings;
use experimental 'signatures';

use Feature::Compat::Try;
use List::Util 'uniqstr';
use Module::Runtime ();
use Module::Pluggable search_path => [qw(
OpenTelemetry::Instrumentation
OpenTelemetry::Integration
)];
use Ref::Util 'is_hashref';

use Log::Any;
my $logger = Log::Any->get_logger( category => 'OpenTelemetry' );

# To be overriden by instrumentations
sub dependencies { }
sub uninstall { } # experimental

my sub module_exists ($module) {
my $file = Module::Runtime::module_notional_filename $module;
for (@INC) {
return 1 if -e "$_/$file";
}
return 0;
}

my @installed;
sub import ( $class, @args ) {
return unless @args;

my $all = $args[0] =~ /^[:-]all$/ && shift @args;

# Inlined from OpenTelemetry::Common to read Perl-specific config
my $legacy_support = $ENV{OTEL_PERL_USE_LEGACY_INSTRUMENTATIONS} // 1;
$legacy_support
= $legacy_support =~ /^true$/i ? 1
: $legacy_support =~ /^false$/i ? 0
: $legacy_support;

my %configuration;
while ( my $key = shift @args ) {
my $options = is_hashref $args[0] ? shift @args : {};

# Legacy namespace support. If we are loading an integration
# by name which does not exist in INC in the new namespace,
# but does exist in the legacy namespace, we use the legacy
# name instead.
my $module = __PACKAGE__ . '::' . $key;
if ( $legacy_support && !module_exists($module) ) {
my $legacy = $module =~ s/^OpenTelemetry::Instrumentation/OpenTelemetry::Integration/r;
$module = $legacy if module_exists($legacy);
}

$configuration{$module} = $options;
}

if ($all) {
for my $plugin ($class->plugins) {
if ( $plugin =~ /^OpenTelemetry::Instrumentation/ ) {
$configuration{$plugin} //= {};
next;
}

next unless $legacy_support;

my $canonical = $plugin =~ s/^OpenTelemetry::Integration/OpenTelemetry::Instrumentation/r;
next if $configuration{$canonical};

$configuration{$plugin} //= {};
}
}

for my $package ( keys %configuration ) {
try {
$logger->tracef('Loading %s', $package);

Module::Runtime::require_module($package);

# We only load dependencies if we are not loading every module
unless ($all) {
Module::Runtime::require_module($_) for $package->dependencies;
}

my $ok = $package->install( %{ $configuration{ $package } } );

if ($ok) {
push @installed, $package;
}
else {
$logger->tracef("$package did not install itself");
}

} catch ($e) {
# Just a warning, if we're loading everything then
# we shouldn't cause chaos just because something
# doesn't happen to be available.
$logger->warnf('Unable to load %s: %s', $package, $e);
}
}
}

sub unimport ( $class, @args ) {
@args = @installed unless @args;
$_->uninstall for @args;
return;
}

1;
130 changes: 130 additions & 0 deletions lib/OpenTelemetry/Instrumentation.pod
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
=encoding utf8

=head1 NAME

OpenTelemetry::Instrumentation - Top-level interface for OpenTelemetry instrumentations

=head1 SYNOPSIS

# Load instrumentations for specific modules
use OpenTelemetry::Instrumentation qw( HTTP::Tiny DBI );

# Some instrumentations can take additional options
use OpenTelemetry::Instrumentation 'HTTP::Tiny' => \%options;

# Load every available instrumentation
use OpenTelemetry::Instrumentation -all;

=head1 DESCRIPTION

This is the base class for handling tracing instrumentation with other CPAN
modules.

It provides functionality for loading available instrumentations
via the import list on a C<use> statement:

=over 4

=item *

with C<-all>, all available OpenTelemetry instrumentations will be loaded,
if the module the instrumentation is for has already been loaded

=item *

with a specific list of modules, they will be loaded (even if they haven't
been before) and instrumentations for them will be applied if available

=back

This means that you can expect L<HTTP::Tiny> to be traced if you have the
L<OpenTelemetry::Instrumentation::HTTP::Tiny> module installed and you do this:

use OpenTelemetry::Instrumentation 'HTTP::Tiny';

or this:

use HTTP::Tiny;
use OpenTelemetry::Instrumentation -all;

but it will B<not> be traced if you do this:

use OpenTelemetry::Instrumentation -all;
use HTTP::Tiny;

The rationale behind this apparently inconsistent behaviour is that, with a
large install, C<:all> might load unexpected instrumentations. This behaviour
allows you instead to add this line after your module imports, and any
functionality that is actively being used in the code (for which an
instrumentation module is available) would gain tracing.

=head2 Legacy namespace

With the release of version 0.026, the namespace for instrumentation
libraries moved from L<OpenTelemetry::Integration> to this one. The legacy
namespace is still supported for backwards compatibility, but should not be
used for new code.

To support it, this module will look for instrumentation libraries in both
namespaces, and prefer the ones in the new namespace over those in the legacy
one. Instrumentation libraries in the legacy namespace will only be loaded if
no equivalent instrumentation exists in the new namespace.

This behaviour is enabled by default, but can be disabled by setting the
C<OTEL_PERL_USE_LEGACY_INSTRUMENTATIONS> environment variable to a false value.

=head1 WRITING INTEGRATIONS

Additional instrumentations can be written and used as long as they are in the
the OpenTelemetry::Instrumentation namespace. They should subclass this module
(OpenTelemetry::Instrumentation) and should implement an C<install> class method
as described in detail below. Other methods described in this section are
optional, but can be used to provide additional features.

=head2 install

$bool = $class->install(%configuration);

Installs the instrumentation. Will be called with a (possibly empty) list of
key/value pairs with configuration options, and is expected to return a
true value if the instrumentation was installed, or false otherwise.

An example implementation of this method might look like the following:

package OpenTelemetry::Instrumentation::Foo::Bar;

use experimental 'signatures';
use Class::Inspector;
use Class::Method::Modifiers 'install_modifier';

my $loaded;
sub install ( $class, %config ) {
return if $loaded++;
return unless Class::Inspector->loaded('Foo::Bar');

install_modifier 'Foo::Bar' => around => reticulate_splines => sub {
my ( $orig, $self, @args ) = @_;
...
my $value = $self->$orig(@args);
...
return $value;
};

return 1;
}

=head2 dependencies

@package_names = $class->dependencies;

Should return the names of the packages that should be loaded to install
this instrumentation. This method will be called in list context when loading
dependencies automatically (ie. not when using the C<all> flag) to load
the dependencies before calling L</install>.

=head1 COPYRIGHT

This software is copyright (c) 2023 by José Joaquín Atria.

This is free software; you can redistribute it and/or modify it under the same
terms as the Perl 5 programming language system itself.
Loading

0 comments on commit 5b0ceb0

Please sign in to comment.