Skip to content

Commit

Permalink
WIP: Add support for logs
Browse files Browse the repository at this point in the history
Note that this branch will be rebased on a periodic basis to stay
on top of changes in main. If you are checking this out early, do
remember to do `git pull -r` to avoid conflicts.
  • Loading branch information
jjatria committed Dec 13, 2023
1 parent ff4e4e6 commit 4a03ae6
Show file tree
Hide file tree
Showing 11 changed files with 412 additions and 58 deletions.
2 changes: 2 additions & 0 deletions Changes
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ Revision history for OpenTelemetry

{{$NEXT}}

* EXPERIMENTAL: Add support for logs

0.019 2023-12-03 14:44:05+00:00 Europe/London

* Document OpenTelemetry->logger as deprecated, as this will soon
Expand Down
88 changes: 54 additions & 34 deletions META.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,133 +79,153 @@
}
},
"provides" : {
"Log::Any::Adapter::OpenTelemetry" : {
"file" : "lib/Log/Any/Adapter/OpenTelemetry.pm",
"version" : "0.020"
},
"OpenTelemetry" : {
"file" : "lib/OpenTelemetry.pm",
"version" : "0.019"
"version" : "0.020"
},
"OpenTelemetry::Attributes" : {
"file" : "lib/OpenTelemetry/Attributes.pm",
"version" : "0.019"
"version" : "0.020"
},
"OpenTelemetry::Baggage" : {
"file" : "lib/OpenTelemetry/Baggage.pm",
"version" : "0.019"
"version" : "0.020"
},
"OpenTelemetry::Constants" : {
"file" : "lib/OpenTelemetry/Constants.pm",
"version" : "0.019"
"version" : "0.020"
},
"OpenTelemetry::Context" : {
"file" : "lib/OpenTelemetry/Context.pm",
"version" : "0.019"
"version" : "0.020"
},
"OpenTelemetry::Exporter" : {
"file" : "lib/OpenTelemetry/Exporter.pm",
"version" : "0.019"
"version" : "0.020"
},
"OpenTelemetry::Integration" : {
"file" : "lib/OpenTelemetry/Integration.pm",
"version" : "0.019"
"version" : "0.020"
},
"OpenTelemetry::Integration::DBI" : {
"file" : "lib/OpenTelemetry/Integration/DBI.pm",
"version" : "0.019"
"version" : "0.020"
},
"OpenTelemetry::Integration::HTTP::Tiny" : {
"file" : "lib/OpenTelemetry/Integration/HTTP/Tiny.pm",
"version" : "0.019"
"version" : "0.020"
},
"OpenTelemetry::Integration::LWP::UserAgent" : {
"file" : "lib/OpenTelemetry/Integration/LWP/UserAgent.pm",
"version" : "0.019"
"version" : "0.020"
},
"OpenTelemetry::Logs::LogRecord::Processor" : {
"file" : "lib/OpenTelemetry/Logs/LogRecord/Processor.pm",
"version" : "0.020"
},
"OpenTelemetry::Logs::Logger" : {
"file" : "lib/OpenTelemetry/Logs/Logger.pm",
"version" : "0.020"
},
"OpenTelemetry::Logs::LoggerProvider" : {
"file" : "lib/OpenTelemetry/Logs/LoggerProvider.pm",
"version" : "0.020"
},
"OpenTelemetry::Processor" : {
"file" : "lib/OpenTelemetry/Processor.pm",
"version" : "0.020"
},
"OpenTelemetry::Propagator" : {
"file" : "lib/OpenTelemetry/Propagator.pm",
"version" : "0.019"
"version" : "0.020"
},
"OpenTelemetry::Propagator::Baggage" : {
"file" : "lib/OpenTelemetry/Propagator/Baggage.pm",
"version" : "0.019"
"version" : "0.020"
},
"OpenTelemetry::Propagator::Composite" : {
"file" : "lib/OpenTelemetry/Propagator/Composite.pm",
"version" : "0.019"
"version" : "0.020"
},
"OpenTelemetry::Propagator::None" : {
"file" : "lib/OpenTelemetry/Propagator/None.pm",
"version" : "0.019"
"version" : "0.020"
},
"OpenTelemetry::Propagator::TextMap" : {
"file" : "lib/OpenTelemetry/Propagator/TextMap.pm",
"version" : "0.019"
"version" : "0.020"
},
"OpenTelemetry::Propagator::TraceContext" : {
"file" : "lib/OpenTelemetry/Propagator/TraceContext.pm",
"version" : "0.019"
"version" : "0.020"
},
"OpenTelemetry::Propagator::TraceContext::TraceFlags" : {
"file" : "lib/OpenTelemetry/Propagator/TraceContext/TraceFlags.pm",
"version" : "0.019"
"version" : "0.020"
},
"OpenTelemetry::Propagator::TraceContext::TraceParent" : {
"file" : "lib/OpenTelemetry/Propagator/TraceContext/TraceParent.pm",
"version" : "0.019"
"version" : "0.020"
},
"OpenTelemetry::Propagator::TraceContext::TraceState" : {
"file" : "lib/OpenTelemetry/Propagator/TraceContext/TraceState.pm",
"version" : "0.019"
"version" : "0.020"
},
"OpenTelemetry::Trace" : {
"file" : "lib/OpenTelemetry/Trace.pm",
"version" : "0.019"
"version" : "0.020"
},
"OpenTelemetry::Trace::Event" : {
"file" : "lib/OpenTelemetry/Trace/Event.pm",
"version" : "0.019"
"version" : "0.020"
},
"OpenTelemetry::Trace::Link" : {
"file" : "lib/OpenTelemetry/Trace/Link.pm",
"version" : "0.019"
"version" : "0.020"
},
"OpenTelemetry::Trace::Span" : {
"file" : "lib/OpenTelemetry/Trace/Span.pm",
"version" : "0.019"
"version" : "0.020"
},
"OpenTelemetry::Trace::Span::Processor" : {
"file" : "lib/OpenTelemetry/Trace/Span/Processor.pm",
"version" : "0.019"
"version" : "0.020"
},
"OpenTelemetry::Trace::Span::Status" : {
"file" : "lib/OpenTelemetry/Trace/Span/Status.pm",
"version" : "0.019"
"version" : "0.020"
},
"OpenTelemetry::Trace::SpanContext" : {
"file" : "lib/OpenTelemetry/Trace/SpanContext.pm",
"version" : "0.019"
"version" : "0.020"
},
"OpenTelemetry::Trace::Tracer" : {
"file" : "lib/OpenTelemetry/Trace/Tracer.pm",
"version" : "0.019"
"version" : "0.020"
},
"OpenTelemetry::Trace::TracerProvider" : {
"file" : "lib/OpenTelemetry/Trace/TracerProvider.pm",
"version" : "0.019"
"version" : "0.020"
},
"OpenTelemetry::X" : {
"file" : "lib/OpenTelemetry/X.pm",
"version" : "0.019"
"version" : "0.020"
},
"OpenTelemetry::X::Invalid" : {
"file" : "lib/OpenTelemetry/X/Invalid.pm",
"version" : "0.019"
"version" : "0.020"
},
"OpenTelemetry::X::Parsing" : {
"file" : "lib/OpenTelemetry/X/Parsing.pm",
"version" : "0.019"
"version" : "0.020"
},
"OpenTelemetry::X::Unsupported" : {
"file" : "lib/OpenTelemetry/X/Unsupported.pm",
"version" : "0.019"
"version" : "0.020"
}
},
"release_status" : "stable",
Expand All @@ -219,7 +239,7 @@
"web" : "https://github.com/jjatria/perl-opentelemetry"
}
},
"version" : "0.019",
"version" : "0.020",
"x_Dist_Zilla" : {
"perl" : {
"version" : "5.036000"
Expand Down Expand Up @@ -450,7 +470,7 @@
"branch" : null,
"changelog" : "Changes",
"signed" : 0,
"tag" : "0.019",
"tag" : "0.020",
"tag_format" : "%v",
"tag_message" : ""
},
Expand Down
104 changes: 104 additions & 0 deletions lib/Log/Any/Adapter/OpenTelemetry.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package Log::Any::Adapter::OpenTelemetry;

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

use Log::Any::Adapter::Util ();
use OpenTelemetry qw( otel_config otel_span_from_context otel_logger_provider );
use Ref::Util 'is_hashref';
use Time::HiRes 'time';

use OpenTelemetry::Constants qw(
LOG_LEVEL_TRACE
LOG_LEVEL_DEBUG
LOG_LEVEL_INFO
LOG_LEVEL_WARN
LOG_LEVEL_ERROR
LOG_LEVEL_FATAL
);

use base 'Log::Any::Adapter::Base';

my %LOG2OTEL = (
trace => LOG_LEVEL_TRACE,
debug => LOG_LEVEL_DEBUG,
info => LOG_LEVEL_INFO,
warn => LOG_LEVEL_WARN,
error => LOG_LEVEL_ERROR,
fatal => LOG_LEVEL_FATAL,
);

my %OTEL2LOG = (
trace => 8,
debug => 7,
info => 6,
warn => 4,
error => 3,
fatal => 2,
);

sub init ( $self, @ ) {
# FIXME: It would be good to get a logger early and cache
# it for eventual calls. However, this suffers from the same
# issue with caching tracers that is documented in the POD
# for OpenTelemetry::Trace::Tracer: namely, that if we get
# the no-op logger before we've set up a real logger provider
# that can generate real loggers, we'll be stuck with a no-op.
# It might be that we need to revisit the proxy classes removed
# in d9e321bd1bf65d510b12ef34fe2b5a0c51da0bf2, although the
# rationale for why they were removed is still sound. We'd just
# have to come up with a way to make sure its delegate continues
# to point to the right place even if the tracer provider changes
# $self->{logger} = otel_logger_provider->logger;
}

for my $method ( Log::Any::Adapter::Util::logging_methods() ) {
no strict 'refs';
*$method = sub ( $self, @args) {
$self->structured( $method, $self->category, @args );
};
}

for my $method ( Log::Any::Adapter::Util::detection_methods() ) {
my $numeric = Log::Any::Adapter::Util::numeric_level( $method =~ s/^is_//r );

no strict 'refs';
*$method = sub {
$numeric <= $OTEL2LOG{ lc( otel_config('LOG_LEVEL') // 'info' ) };
};
}

sub structured ( $self, $method, $category, @parts ) {
my $level = $method;
for ($level) {
s/(?:emergency|alert|critical)/fatal/;
s/notice/info/;
s/warning/warn/;
}

# FIXME: This is a little finicky. The aim is for the first
# argument to be the body (even if it is structured), and
# anything else gets put into the attributes. If the log
# comes with structured data that is not a hash, we put it
# under a `payload` key. Maybe this can be simplified to
# always put the data under a given key, but then we add
# data to the arguably common operation of attaching a hash.
my %args = ( body => shift @parts );

$args{attributes} = @parts == 1
? is_hashref $parts[0]
? $parts[0] : { payload => $parts[0] }
: @parts % 2
? { payload => \@parts } : { @parts }
if @parts;

otel_logger_provider->logger->emit_record(
timestamp => time,
severity_text => $method,
severity_number => 0+$LOG2OTEL{$level},
%args,
);
}

1;
34 changes: 34 additions & 0 deletions lib/OpenTelemetry.pm
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use OpenTelemetry::Common;
use OpenTelemetry::Context;
use OpenTelemetry::Propagator::None;
use OpenTelemetry::Trace::TracerProvider;
use OpenTelemetry::Logs::LoggerProvider;
use OpenTelemetry::X;
use Scalar::Util 'refaddr';
use Ref::Util 'is_coderef';
Expand All @@ -26,6 +27,7 @@ use Exporter::Shiny qw(
otel_error_handler
otel_handle_error
otel_logger
otel_logger_provider
otel_propagator
otel_span_from_context
otel_tracer_provider
Expand Down Expand Up @@ -55,6 +57,38 @@ sub _generate_otel_logger { \&logger }
sub tracer_provider :lvalue { sentinel get => sub { $instance }, set => $set }
}

# FIXME: The functions in this block mean that in all likelihood
# OpenTelemetry->logger will have to go away. It no longer fits,
# since we gain the concept of a logger, which is different from
# the logger we used to return (which is just a Log::Any logger).
# This is not such a big problem: it just means that users who
# want to use the same logger as the rest of the OpenTelemetry
# implementation need to do
#
# my $log = Log::Any->get_logger( category => 'OpenTelemetry' );
#
# but you can argue that that's not so onerous, and if it is, that
# logging on that given category rather than the default one is
# a pointless.
{
my $lock = Mutex->new;
my $instance = OpenTelemetry::Logs::LoggerProvider->new;

my $set = sub ( $new ) {
die OpenTelemetry::X->create(
Invalid => 'Global logger provider must be a subclass of OpenTelemetry::Logs::LoggerProvider, got instead ' . ( ref $new || 'a plain scalar' ),
) unless $new isa OpenTelemetry::Logs::LoggerProvider;

$lock->enter( sub { $instance = $new });
};

sub _generate_otel_logger_provider {
my $x = sub :lvalue { sentinel get => sub { $instance }, set => $set };
}

sub logger_provider :lvalue { sentinel get => sub { $instance }, set => $set }
}

{
my $lock = Mutex->new;
my $instance = OpenTelemetry::Propagator::None->new;
Expand Down
Loading

0 comments on commit 4a03ae6

Please sign in to comment.