File Coverage

blib/lib/HealthCheck.pm
Criterion Covered Total %
statement 133 133 100.0
branch 71 76 93.4
condition 28 34 82.3
subroutine 17 17 100.0
pod 5 5 100.0
total 254 265 95.8


line stmt bran cond sub pod time code
1             package HealthCheck;
2 1     1   226323 use parent 'HealthCheck::Diagnostic';
  1         327  
  1         5  
3              
4             # ABSTRACT: A health check for your code
5 1     1   48 use version;
  1         2  
  1         4  
6             our $VERSION = 'v1.7.0'; # VERSION
7              
8 1     1   81 use 5.010;
  1         4  
9 1     1   5 use strict;
  1         2  
  1         19  
10 1     1   6 use warnings;
  1         2  
  1         22  
11              
12 1     1   5 use Carp;
  1         2  
  1         49  
13              
14 1     1   579 use Hash::Util::FieldHash;
  1         913  
  1         52  
15 1     1   7 use List::Util qw(any);
  1         2  
  1         1616  
16              
17             # Create a place outside of $self to store the checks
18             # as everything in the self hashref will be copied into
19             # the result.
20             Hash::Util::FieldHash::fieldhash my %registered_checks;
21              
22             #pod =head1 SYNOPSIS
23             #pod
24             #pod use HealthCheck;
25             #pod
26             #pod # a check can return a hashref containing anything at all,
27             #pod # however some values are special.
28             #pod # See the HealthCheck Standard for details.
29             #pod sub my_check {
30             #pod return {
31             #pod anything => "at all",
32             #pod id => "my_check",
33             #pod status => 'WARNING',
34             #pod };
35             #pod }
36             #pod
37             #pod my $checker = HealthCheck->new(
38             #pod id => 'main_checker',
39             #pod label => 'Main Health Check',
40             #pod tags => [qw( fast cheap )],
41             #pod checks => [
42             #pod sub { return { id => 'coderef', status => 'OK' } },
43             #pod 'my_check', # Name of a method on caller
44             #pod ],
45             #pod );
46             #pod
47             #pod my $other_checker = HealthCheck->new(
48             #pod id => 'my_health_check',
49             #pod label => "My Health Check",
50             #pod tags => [qw( cheap easy )],
51             #pod other => "Other details to pass to the check call",
52             #pod )->register(
53             #pod 'My::Checker', # Name of a loaded class that ->can("check")
54             #pod My::Checker->new, # Object that ->can("check")
55             #pod );
56             #pod
57             #pod # It's possible to add ids, labels, and tags to your checks
58             #pod # and they will be copied to the Result.
59             #pod $other_checker->register( My::Checker->new(
60             #pod id => 'my_checker',
61             #pod label => 'My Checker',
62             #pod tags => [qw( cheap copied_to_the_result )],
63             #pod ) );
64             #pod
65             #pod # You can add HealthCheck instances as checks
66             #pod # You could add a check to itself to create an infinite loop of checks.
67             #pod $checker->register( $other_checker );
68             #pod
69             #pod # A hashref of the check config
70             #pod # This whole hashref is passed as an argument
71             #pod # to My::Checker->another_check
72             #pod $checker->register( {
73             #pod invocant => 'My::Checker', # to call the "check" on
74             #pod check => 'another_check', # name of the check method
75             #pod tags => [qw( fast easy )],
76             #pod more_params => 'anything',
77             #pod } );
78             #pod
79             #pod my @tags = $checker->tags; # returns fast, cheap
80             #pod
81             #pod my %result = %{ $checker->check( tags => ['cheap'] ) };
82             #pod # OR run the opposite checks
83             #pod %result = %{ $checker->check( tags => ['!cheap'] ) };
84             #pod
85             #pod # A checker class or object just needs to have either
86             #pod # a check method, which is used by default,
87             #pod # or another method as specified in a hash config.
88             #pod package My::Checker;
89             #pod
90             #pod # Optionally subclass HealthCheck::Diagnostic
91             #pod use parent 'HealthCheck::Diagnostic';
92             #pod
93             #pod # and provide a 'run' method, the Diagnostic base class will
94             #pod # pass your results through the 'summarize' helper that
95             #pod # will add warnings about invalid values as well as
96             #pod # summarizing multiple results.
97             #pod sub run {
98             #pod return {
99             #pod id => ( ref $_[0] ? "object_method" : "class_method" ),
100             #pod status => "WARNING",
101             #pod };
102             #pod }
103             #pod
104             #pod # Any checks *must* return a valid "Health Check Result" hashref.
105             #pod
106             #pod # You can add your own check that doesn't call 'summarize'
107             #pod # or, overload the 'check' helper in the parent class.
108             #pod sub another_check {
109             #pod my ($self, %params) = @_;
110             #pod return {
111             #pod id => 'another_check',
112             #pod label => 'A Super custom check',
113             #pod status => ( $params{more_params} eq 'fine' ? "OK" : "CRITICAL" ),
114             #pod };
115             #pod }
116             #pod
117             #pod C<%result> will be from the subset of checks run due to the tags.
118             #pod
119             #pod $checker->check(tags => ['cheap']);
120             #pod
121             #pod id => "main_checker",
122             #pod label => "Main Health Check",
123             #pod tags => [ "fast", "cheap" ],
124             #pod status => "WARNING",
125             #pod results => [
126             #pod { id => "coderef",
127             #pod status => "OK",
128             #pod tags => [ "fast", "cheap" ] # inherited
129             #pod },
130             #pod { anything => "at all",
131             #pod id => "my_check",
132             #pod status => "WARNING",
133             #pod tags => [ "fast", "cheap" ] # inherited
134             #pod },
135             #pod { id => "my_health_check",
136             #pod label => "My Health Check",
137             #pod tags => [ "cheap", "easy" ],
138             #pod status => "WARNING",
139             #pod results => [
140             #pod { id => "class_method",
141             #pod tags => [ "cheap", "easy" ],
142             #pod status => "WARNING",
143             #pod },
144             #pod { id => "object_method",
145             #pod tags => [ "cheap", "easy" ],
146             #pod status => "WARNING",
147             #pod },
148             #pod { id => "object_method_1",
149             #pod label => "My Checker",
150             #pod tags => [ "cheap", "copied_to_the_result" ],
151             #pod status => "WARNING",
152             #pod }
153             #pod ],
154             #pod }
155             #pod ],
156             #pod
157             #pod There is also runtime support,
158             #pod which can be enabled by adding a truthy C param to the C.
159             #pod
160             #pod $checker->check( tags => [ 'easy', '!fast' ], runtime => 1 );
161             #pod
162             #pod id => "my_health_check",
163             #pod label => "My Health Check",
164             #pod runtime => "0.000",
165             #pod tags => [ "cheap", "easy" ],
166             #pod status => "WARNING",
167             #pod results => [
168             #pod { id => "class_method",
169             #pod runtime => "0.000",
170             #pod tags => [ "cheap", "easy" ],
171             #pod status => "WARNING",
172             #pod },
173             #pod { id => "object_method",
174             #pod runtime => "0.000",
175             #pod tags => [ "cheap", "easy" ],
176             #pod status => "WARNING",
177             #pod }
178             #pod ],
179             #pod
180             #pod =head1 DESCRIPTION
181             #pod
182             #pod Allows you to create callbacks that check the health of your application
183             #pod and return a status result.
184             #pod
185             #pod There are several things this is trying to enable:
186             #pod
187             #pod =over
188             #pod
189             #pod =item *
190             #pod
191             #pod A fast HTTP endpoint that can be used to verify that a web app can
192             #pod serve traffic.
193             #pod To this end, it may be useful to use the runtime support option,
194             #pod available in L.
195             #pod
196             #pod =item *
197             #pod A more complete check that verifies all the things work after a deployment.
198             #pod
199             #pod =item *
200             #pod
201             #pod The ability for a script, such as a cronjob, to verify that it's dependencies
202             #pod are available before starting work.
203             #pod
204             #pod =item *
205             #pod
206             #pod Different sorts of monitoring checks that are defined in your codebase.
207             #pod
208             #pod =back
209             #pod
210             #pod Results returned by these checks should correspond to the GSG
211             #pod L.
212             #pod
213             #pod You may want to use L to simplify writing your
214             #pod check slightly.
215             #pod
216             #pod =head1 METHODS
217             #pod
218             #pod =head2 new
219             #pod
220             #pod my $checker = HealthCheck->new( id => 'my_checker' );
221             #pod
222             #pod =head3 ATTRIBUTES
223             #pod
224             #pod =over
225             #pod
226             #pod =item checks
227             #pod
228             #pod An arrayref that is passed to L to initialize checks.
229             #pod
230             #pod =item tags
231             #pod
232             #pod An arrayref used as the default set of tags for any checks that don't
233             #pod override them.
234             #pod
235             #pod =back
236             #pod
237             #pod Any other parameters are included in the "Result" hashref returned.
238             #pod
239             #pod Some recommended things to include are:
240             #pod
241             #pod =over
242             #pod
243             #pod =item id
244             #pod
245             #pod The unique id for this check.
246             #pod
247             #pod =item label
248             #pod
249             #pod A human readable name for this check.
250             #pod
251             #pod =back
252             #pod
253             #pod =cut
254              
255             sub new {
256 35     35 1 27861 my ( $class, %params ) = @_;
257 35         76 my $checks = delete $params{checks};
258 35         90 my $self = bless {%params}, $class;
259 35 100       194 return $checks ? $self->register($checks) : $self;
260             }
261              
262             #pod =head2 register
263             #pod
264             #pod $checker->register({
265             #pod invocant => $class_or_object,
266             #pod check => $method_on_invocant_or_coderef,
267             #pod more => "any other params are passed to the check",
268             #pod });
269             #pod
270             #pod Takes a list or arrayref of check definitions to be added to the object.
271             #pod
272             #pod Each registered check must return a valid GSG Health Check response,
273             #pod either as a hashref or an even-sized list.
274             #pod See the GSG Health Check Standard (linked in L)
275             #pod for the fields that checks should return.
276             #pod
277             #pod Rather than having to always pass in the full hashref definition,
278             #pod several common cases are detected and used to fill out the check.
279             #pod
280             #pod =over
281             #pod
282             #pod =item coderef
283             #pod
284             #pod If passed a coderef, this will be called as the C without an C.
285             #pod
286             #pod =item object
287             #pod
288             #pod If a blessed object is passed in
289             #pod and it has a C method, use that for the C,
290             #pod otherwise throw an exception.
291             #pod
292             #pod =item string
293             #pod
294             #pod If a string is passed in,
295             #pod check if it is the name of a loaded class that has a C method,
296             #pod and if so use it as the C with the method as the C.
297             #pod Otherwise if our L has a method with this name,
298             #pod the L becomes the C and this becomes the C,
299             #pod otherwise throws an exception.
300             #pod
301             #pod =item full hashref of params
302             #pod
303             #pod The full hashref can consist of a C key that the above heuristics
304             #pod are applied,
305             #pod or include an C key that is used as either
306             #pod an C or C.
307             #pod With the C specified, the now optional C key
308             #pod defaults to "check" and is used as the method to call on C.
309             #pod
310             #pod All attributes other than C and C are passed to the check.
311             #pod
312             #pod =back
313             #pod
314             #pod =cut
315              
316             sub register {
317 29     29 1 6990 my ($self, @checks) = @_;
318 29 100       278 croak("register cannot be called as a class method") unless ref $self;
319 28 50       60 return $self unless @checks;
320 28         49 my $class = ref $self;
321              
322 28 100 100     156 @checks = @{ $checks[0] }
  12   66     28  
323             if @checks == 1 and ( ref $checks[0] || '' ) eq 'ARRAY';
324              
325             # If the check that was passed in is just the name of a method
326             # we are going to use our caller as the invocant.
327 28         55 my $caller;
328             my $find_caller = sub {
329 7     7   13 my ( $i, $c ) = ( 1, undef );
330 7         10 do { ($c) = caller( $i++ ) } while $c->isa(__PACKAGE__);
  10         92  
331 7         25 $c;
332 28         116 };
333              
334 28         110 foreach (@checks) {
335 44   100     110 my $type = ref $_ || '';
336             my %c
337 44 50       131 = $type eq 'HASH' ? ( %{$_} )
  20 100       59  
338             : $type eq 'ARRAY' ? ( check => $class->register($_) )
339             : ( check => $_ );
340              
341 44 100       373 croak("check parameter required") unless $c{check};
342              
343             # If it's not a coderef,
344             # it must be the name of a method to call on an invocant.
345 41 100 100     118 unless ( ( ref $c{check} || '' ) eq 'CODE' ) {
346              
347             # If they passed in an object or a class that can('check')
348             # then we want to set that as the invocant so the check
349             # runner does the right thing.
350 13 100 66     59 if ( $c{check} and not $c{invocant} and do {
      100        
351 11         14 local $@;
352 11         21 eval { local $SIG{__DIE__}; $c{check}->can('check') };
  11         34  
  11         110  
353             } )
354             {
355 4         8 $c{invocant} = $c{check};
356 4         38 $c{check} = 'check';
357             }
358              
359             # If they just passed in a method name,
360             # we can see if the caller has that method.
361 13 100       32 unless ($c{invocant}) {
362 7   33     23 $caller ||= $find_caller->();
363              
364 7 100       50 if ($caller->can($c{check}) ) {
365 4         10 $c{invocant} = $caller;
366             }
367             else {
368 3         292 croak("Can't determine what to do with '$c{check}'");
369             }
370             }
371              
372             croak("'$c{invocant}' cannot '$c{check}'")
373 10 100       220 unless $c{invocant}->can( $c{check} );
374             }
375              
376 36         52 push @{ $registered_checks{$self} }, \%c;
  36         214  
377             }
378              
379 20         175 return $self;
380             }
381              
382             #pod =head2 check
383             #pod
384             #pod my %results = %{ $checker->check(%params) }
385             #pod
386             #pod Calls all of the registered checks and returns a hashref of the results of
387             #pod processing the checks passed through L.
388             #pod Passes the L as an even-sized list to the check,
389             #pod without the C or C keys.
390             #pod This hashref is shallow merged with and duplicate keys overridden by
391             #pod the C<%params> passed in.
392             #pod
393             #pod If there is both an C and C in the params,
394             #pod it the C is called as a method on the C,
395             #pod otherwise C is used as a callback coderef.
396             #pod
397             #pod If only a single check is registered,
398             #pod the results from that check are merged with, and will override
399             #pod the L set on the object instead of being put in
400             #pod a C arrayref.
401             #pod
402             #pod Throws an exception if no checks have been registered.
403             #pod
404             #pod =head3 run
405             #pod
406             #pod Main implementation of the checker is here.
407             #pod
408             #pod Passes C<< summarize_result => 0 >> to each registered check
409             #pod unless overridden to avoid running C multiple times.
410             #pod See L.
411             #pod
412             #pod =cut
413              
414             sub check {
415 27     27 1 1737 my ( $self, @params ) = @_;
416 27 100       161 croak("check cannot be called as a class method") unless ref $self;
417 26 100       39 croak("No registered checks") unless @{ $registered_checks{$self} || [] };
  26 100       174  
418 25         108 $self->SUPER::check(@params);
419             }
420              
421             sub run {
422 25     25 1 47 my ($self, %params) = @_;
423              
424             # If we are going to summarize things, no need for our children to
425 25 100       62 $params{summarize_result} = 0 unless exists $params{summarize_result};
426              
427             my @results = map {
428 41         61 my %c = %{$_};
  41         112  
429 41         116 $self->_set_check_response_defaults(\%c);
430 41         79 my $defaults = delete $c{_respond};
431 41   100     112 my $i = delete $c{invocant} || '';
432 41   50     91 my $m = delete $c{check} || '';
433              
434 41         52 my @r;
435             # Exceptions will probably not contain child health check's metadata,
436             # as HealthCheck::Diagnostic->summarize would normally populate these
437             # and was not called.
438             # This could theoretically be a pain for prodsupport. If we find this
439             # happening frequently, we should reassess our decision not to attempt
440             # to call summarize here
441             # (for fear of exception-catching magic and rabbitholes).
442             {
443 41         47 local $@;
  41         64  
444 41         62 @r = eval { local $SIG{__DIE__};
  41         129  
445 41 100       178 $i ? $i->$m( %c, %params ) : $m->( %c, %params ) };
446 41 100 66     344 @r = { status => 'CRITICAL', info => $@ } if $@ and not @r;
447             }
448              
449             @r
450             = @r == 1 && ref $r[0] eq 'HASH' ? $r[0]
451             : @r % 2 == 0 ? {@r}
452 41 100 100     177 : do {
    100          
453 2 100       7 my $c = $i ? "$i->$m" : "$m";
454 2         209 carp("Invalid return from $c (@r)");
455 2         63 ();
456             };
457              
458 41 100       86 if (@r) { @r = +{ %$defaults, %{ $r[0] } } }
  39         78  
  39         112  
459              
460 41         179 @r;
461             } grep {
462 54         127 $self->should_run( $_, %params );
463 25 50       52 } @{ $registered_checks{$self} || [] };
  25         74  
464              
465 25 100       62 return unless @results; # don't return undef, instead an empty list
466 24 50       35 return $results[0] if @{ $registered_checks{$self} || [] } == 1;
  24 100       122  
467 10         51 return { results => \@results };
468             }
469              
470             sub _set_check_response_defaults {
471 105     105   166 my ($self, $c) = @_;
472 105 100       214 return if exists $c->{_respond};
473              
474 48         60 my %defaults;
475 48         74 FIELD: for my $field ( qw(id label tags) ) {
476 144 100       248 if (exists $c->{$field}) {
477 27         46 $defaults{$field} = $c->{$field};
478 27         48 next FIELD;
479             }
480              
481 117 100 100     330 if ( $c->{invocant} && $c->{invocant}->can($field) ) {
482 15         16 my $val;
483 15 100       30 if ( $field eq 'tags' ) {
484 5 100       12 if (my @tags = $c->{invocant}->$field) {
485 3         7 $val = [@tags];
486             }
487             }
488             else {
489 10         26 $val = $c->{invocant}->$field;
490             }
491              
492 15 100       30 if (defined $val) {
493 7         23 $defaults{$field} = $val;
494 7         16 next FIELD;
495             }
496             }
497              
498             # we only copy tags from the checker to the sub-checks,
499             # and only if they don't exist.
500 110 100       242 $self->_set_default_fields(\%defaults, $field)
501             if $field eq 'tags';
502             }
503              
504             # deref the tags, just in case someone decides to adjust them later.
505 48 100       106 $defaults{tags} = [ @{ $defaults{tags} } ] if $defaults{tags};
  26         48  
506              
507 48         94 $c->{_respond} = \%defaults;
508             }
509              
510              
511             #pod =head1 INTERNALS
512             #pod
513             #pod These methods may be useful for subclassing,
514             #pod but are not intended for general use.
515             #pod
516             #pod =head2 should_run
517             #pod
518             #pod my $bool = $checker->should_run( \%check, tags => ['apple', '!banana'] );
519             #pod
520             #pod Takes a check definition hash and paramters and returns true
521             #pod if the check should be run.
522             #pod Used by L to determine which checks to run.
523             #pod
524             #pod Supported parameters:
525             #pod
526             #pod =over
527             #pod
528             #pod =item tags
529             #pod
530             #pod Tags can be either "positive" or "negative". A negative tag is indicated by a
531             #pod leading C.
532             #pod A check is run if its tags match any of the passed in positive tags and none
533             #pod of the negative ones.
534             #pod If no tags are passed in, all checks will be run.
535             #pod
536             #pod If the C C and there are no tags in the
537             #pod L then the return value of that method is used.
538             #pod
539             #pod If a check has no tags defined, will use the default tags defined
540             #pod when the object was created.
541             #pod
542             #pod =back
543             #pod
544             #pod =cut
545              
546             sub _has_tags {
547 64     64   115 my ($self, $check, @want_tags) = @_;
548              
549 64         122 $self->_set_check_response_defaults($check);
550              
551             # Look at what the check responds to, not what was initially specified
552             # (in case tags are inherited)
553 64 50       77 my %have_tags = map { $_ => 1 } @{ $check->{_respond}{tags} || [] };
  99         235  
  64         145  
554              
555 64     69   220 return any { $have_tags{$_} } @want_tags;
  69         319  
556             }
557              
558             sub should_run {
559 102     102 1 285 my ( $self, $check, %params ) = @_;
560              
561 102         142 my (@positive_tags, @negative_tags);
562 102         125 for my $tag ( @{ $params{tags} } ) {
  102         186  
563 72 100       154 if ( $tag =~ /^!/ ) {
564 12         30 push @negative_tags, substr($tag, 1);
565             }
566             else {
567 60         111 push @positive_tags, $tag;
568             }
569             }
570              
571 102 100 100     228 return 0 if @negative_tags && $self->_has_tags($check, @negative_tags);
572 99 100       257 return 1 unless @positive_tags;
573 52         99 return $self->_has_tags($check, @positive_tags);
574             }
575              
576             1;
577              
578             __END__