File Coverage

blib/lib/Metrics/Any/Adapter/Routable.pm
Criterion Covered Total %
statement 81 81 100.0
branch 14 16 87.5
condition 5 5 100.0
subroutine 22 22 100.0
pod 0 13 0.0
total 122 137 89.0


line stmt bran cond sub pod time code
1             # You may distribute under the terms of either the GNU General Public License
2             # or the Artistic License (the same terms as Perl itself)
3             #
4             # (C) Paul Evans, 2020-2024 -- leonerd@leonerd.org.uk
5              
6             package Metrics::Any::Adapter::Routable 0.02;
7              
8 4     4   1582986 use v5.24; # postfix deref
  4         16  
9 4     4   40 use warnings;
  4         19  
  4         324  
10 4     4   2144 use experimental 'signatures';
  4         7015  
  4         22  
11              
12 4     4   3009 use meta 0.008;
  4         4973  
  4         174  
13 4     4   28 no warnings 'meta::experimental';
  4         8  
  4         215  
14              
15 4     4   21 use Carp;
  4         7  
  4         292  
16 4     4   23 use List::Util 1.39 qw( any pairs );
  4         101  
  4         294  
17 4     4   1986 use Syntax::Keyword::Try 0.18;
  4         8334  
  4         22  
18              
19             require Metrics::Any::Adapter; Metrics::Any::Adapter->VERSION( '0.06' );
20              
21             =head1 NAME
22              
23             C - configurable routing of reported metrics
24              
25             =head1 SYNOPSIS
26              
27             =for highlighter language=perl
28              
29             use Metrics::Any::Adapter 'Routable',
30             targets => [
31             [ "important", "Statsd" ],
32             [ "default", "Prometheus" ],
33             [ ["default", "debug"], "File", path => "metrics.log" ],
34             ],
35             packages => {
36             "My::Application" => "important",
37             "Net::Async::HTTP" => "debug",
38             "IO::Async::*" => "debug", # wildcard matches
39             # anything else will be assigned "default"
40             };
41              
42             =head1 DESCRIPTION
43              
44             This L adapter type acts as a proxy for a set of multiple other
45             adapters, allowing an application to configure which adapter (or adapters) to
46             send particular metrics into.
47              
48             Routing of metrics is done by a "category" name. Each reported metric is
49             assigned into a category, which is a string. Each configured adapter declares
50             an interest in one or more category names. Reported metrics are then routed
51             only to those adapters which declared an interest in the category.
52              
53             Primarily the category names are set by the C configuration
54             argument. Additionally, this can be overridden by any individual metric when
55             it is constructed by providing a C parameter to the C method
56             which created it.
57              
58             =head1 ARGUMENTS
59              
60             The following additional arguments are recognised
61              
62             =head2 targets
63              
64             targets => [
65             [ $category, $type, ],
66             [ $category, $type, @args ],
67             [ [ @categories ], $type, @args ],
68             ...
69             ],
70              
71             A reference to an array containing a list of targets. Each target consists of
72             a category name (or reference array containing a list of categories), a type
73             name, and an optional set of constructor arguments, all stored in its own
74             array reference.
75              
76             These targets will all be constructed and stored by the adapter.
77              
78             =head2 packages
79              
80             packages => {
81             $package => $category,
82             ...
83             }
84              
85             A reference to a hash associating a category name with a reporting package.
86             Any metrics registered by the given package will be associated with the given
87             category name.
88              
89             A pattern can also be specified with a trailing C<::*> wildcard; this will
90             match any package name within the given namespace. Longer matches will take
91             precedence over shorter ones.
92              
93             Any reported metric that does not otherwise have a category configured will be
94             assigned the category C.
95              
96             =cut
97              
98 3         8 sub new ( $class, %args )
99 3     3 0 36 {
  3         8  
  3         5  
100 3         12 my $self = bless {
101             package_category => {},
102             metric_category => {},
103             targets => [],
104             }, $class;
105              
106 3         43 $self->add_target( @$_ ) for $args{targets}->@*;
107              
108 3         36 $self->set_category_for_package( $_->key, $_->value ) for pairs $args{packages}->%*;
109              
110 3         25 return $self;
111             }
112              
113 9         15 sub add_target ( $self, $categories, $type, @args )
  9         15  
  9         16  
114 9     9 0 14 {
  9         12  
  9         42  
115 9 100       39 ref $categories eq "ARRAY" or $categories = [ $categories ];
116              
117 9         29 my $adapter = Metrics::Any::Adapter->class_for_type( $type )->new( @args );
118              
119 9         174 push $self->{targets}->@*, [ $categories, $adapter ];
120             }
121              
122 6         11 sub category_for_package ( $self, $package )
123 6     6 0 12414 {
  6         10  
  6         8  
124 6         11 my $categories = $self->{package_category};
125              
126 6 100       25 return $categories->{$package} if exists $categories->{$package};
127              
128 4         14 while( length $package ) {
129 5 100       30 return $categories->{"${package}::*"} if exists $categories->{"${package}::*"};
130 3 100       22 $package =~ s/::[^:]+$// or last;
131             }
132 2         12 return undef;
133             }
134              
135 3         3 sub set_category_for_package ( $self, $package, $category )
  3         4  
136 3     3 0 3 {
  3         2  
  3         4  
137 3         32 $self->{package_category}{$package} = $category;
138             }
139              
140             my $metapkg = meta::get_this_package;
141              
142             foreach my $method (qw( make_counter make_distribution make_gauge make_timer )) {
143 5     5 0 188 my $code = sub ( $self, $handle, %args ) {
  5     5 0 8  
  5     5 0 10  
  5     5 0 11  
  5         7  
144 5         8 my $collector = $args{collector};
145              
146             $self->{metric_category}{$handle} = $args{category} //
147 5   100     25 $self->category_for_package( $collector->package ) //
      100        
148             # TODO: a configurable default category
149             "default";
150              
151 5         7 my @errs;
152 5         12 foreach my $target ( $self->{targets}->@* ) {
153 15         39 my ( undef, $adapter ) = @$target;
154              
155             try {
156             $adapter->$method( $handle, %args );
157             }
158 15         24 catch ( $e ) {
159             push @errs, $e;
160             }
161             }
162 5 50       30 die $errs[0] if @errs;
163             };
164              
165             $metapkg->add_named_sub( $method => $code );
166             }
167              
168             foreach my $method (qw( inc_counter_by report_distribution inc_gauge_by set_gauge report_timer )) {
169 5     5 0 76 my $code = sub ( $self, $handle, @args ) {
  5     5 0 9  
  5     5 0 7  
  5     5 0 9  
  5     5 0 7  
170 5 50       16 my $category = $self->{metric_category}{$handle} or
171             croak "Unsure category for $handle";
172              
173 5         8 my @errs;
174 5         11 foreach my $target ( $self->{targets}->@* ) {
175 15         48 my ( $categories, $adapter ) = @$target;
176              
177 15 100   17   57 next unless any { $_ eq $category } @$categories;
  17         43  
178              
179             try {
180             $adapter->$method( $handle, @args );
181             }
182 11         24 catch ( $e ) {
183             push @errs, $e;
184             }
185             }
186 5 100       34 die $errs[0] if @errs;
187             };
188              
189             $metapkg->add_named_sub( $method => $code );
190             }
191              
192             =head1 AUTHOR
193              
194             Paul Evans
195              
196             =cut
197              
198             0x55AA;