File Coverage

blib/lib/HealthCheck.pm
Criterion Covered Total %
statement 135 135 100.0
branch 71 76 93.4
condition 28 34 82.3
subroutine 19 19 100.0
pod 5 5 100.0
total 258 269 95.9


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