# -*- Mode: perl -*- # # $Id: Cfg.pm,v 0.1.1.1 2001/07/13 17:05:28 ram Exp $ # # Copyright (c) 2000-2001, Christophe Dehaudt & Raphael Manfredi # # You may redistribute only under the terms of the Artistic License, # as specified in the README file that comes with the distribution. # # HISTORY # $Log: Cfg.pm,v $ # Revision 0.1.1.1 2001/07/13 17:05:28 ram # patch2: random cleanup (from CDE) # # Revision 0.1 2001/03/31 10:04:36 ram # Baseline for first Alpha release. # # $EndLog$ # use strict; package Carp::Datum::Cfg; use Carp::Datum::Flags; use Getargs::Long qw(ignorecase); require Exporter; use vars qw(@ISA @EXPORT); @ISA = qw(Exporter); @EXPORT = (qw( ), @Carp::Datum::Flags::EXPORT); use vars qw($DEBUG_TABLE); # # Structure of the hash ref that is returned by the parser: # # FLAG_SETTING: # { debug => [ DTM_SET, DTM_CLEAR ], # trace => [ DTM_SET, DTM_CLEAR ], # args => VAL # } # # debug and trace correspond to a two values array. First value is the # set mask and the second is the clear one. # # args indicates the maximum number of arguments that is printed # during the tracing of the flow. -1 means all arguments. # # # DEBUG_TABLE: # { default => FLAG_SETTING, # # routine => { "routine_name1" => FLAG_SETTING, # "routine_name2" => FLAG_SETTING, # .... # }, # # file => { flags => { "path1" => FLAG_SETTING, # "path2" => FLAG_SETTING, # .... # }, # routine => { "routine_name1" => FLAG_SETTING, # "routine_name2" => FLAG_SETTING, # .... # } # }, # # type => { flags => { "type1" => FLAG_SETTING, # "type2" => FLAG_SETTING, # .... # }, # routine => { "routine_name1" => FLAG_SETTING, # "routine_name2" => FLAG_SETTING, # .... # } # }, # # alias => [ [ "path1", "alias1" ], # [ "path2", "alias2" ], # .... # ], # # define => { "name1" => FLAG_SETTING, # "name2" => FLAG_SETTING, # .... # } # } # # # default debug table $DEBUG_TABLE = {default => { debug => [DBG_ALL, 0], trace => [TRC_ALL, 0], args => -1 }, alias => [] }; # # ->make # # # Arguments: # -file => $filename: file to load [optionnal] # -config => $string: string which contains config set up [optionnal] # sub make { my $self = bless {}, shift; my ($filename, $raw_config) = cgetargs(@_, [qw(file config)]); $self->{cfg_table} = $DEBUG_TABLE; local $_ = ''; if (defined $filename && open(XFILE, $filename)) { $_ = "\n" . join('', ); die $@ if $@; close XFILE; } if (defined $raw_config) { $_ .= "\n" . $raw_config; $filename .= " + " if defined $filename; $filename .= "'RAW DATA CONFIGURATION'"; } # to prevent the parsing when the given parameter is a fake # filename, there is a test on the string to parse. It must # contain a blank character to possibly be parsed. A non existing # path will not contain this character. if (/\s/) { # use the parser to populate the debug tree structure my $p = Carp::Datum::Parser->new(\&Carp::Datum::Parser::yylex, \&Carp::Datum::Parser::yyerror, 0); $p->init_parser($filename); my $result = $p->yyparse(); # add the default values to the result if they have not been # set during the parsing while (my ($k, $v) = each %$DEBUG_TABLE) { $result->{$k} = $v unless defined $result->{$k}; } $self->{cfg_table} = $result; } # separate the result in different attibutes to speed-up the # processing (one dereference is saved). That is also beautifying # the code. $self->{cfg_file} = $self->cfg_table->{file}; $self->{cfg_routine} = $self->cfg_table->{routine}; $self->{cfg_cluster} = $self->cfg_table->{cluster}; $self->{cfg_type} = $self->cfg_table->{type}; $self->{cfg_alias} = $self->cfg_table->{alias}; return $self; } ######################################################################### # Internal Attribute Access: these methods are not intended to be used # # from the external of the object. # ######################################################################### sub cfg_table {$_[0]->{cfg_table}} sub cfg_alias {$_[0]->{cfg_alias}} # # ->basename # sub basename { my $name = shift; my $result = $name; if ($name =~ /\//) { ($result) = $name =~ /.*\/(\S+)/; } return $result; } # # ->add_flag # # static class function that is used by the flag routine when additive # method is requested for flag computation. # # Arguments: # $old: old value, # $new: new value (can be undef or null) # # Returns: # the clear bits of new are cleared on old and set bits of new are # set on old. # sub add_flag { my ($old, $new) = @_; if (defined $new && $new != 0) { return $old & ~$new->[DTM_CLEAR] | $new->[DTM_SET]; } return $old; } # # ->add_args # # static class function that is used by the flag routine when replacing # method is requested for flag computation. # # Arguments: # $old: old value, # $new: new value (can be undef or null) # # Returns: # the new value if defined # sub add_args { my ($old, $new) = @_; return $old unless defined $new; return $new; } ######################################################################### # Class Feature: usable from the external world # ######################################################################### # # ->check_debug # # return true when the given mask matches the flag setting for debug # mode # # Arguments: # $mask: bit field that is compared to the setting. # # $caller_penalty: [optional] allows to provide a penalty used to # determine the function features (via caller()) that is used to get # the configuration setting. When not specified or 0, the call level # right above the function that call the check_debug (2 steps from # here) will be used. # # Returns: # a boolean value. # sub check_debug { return $_[0]->flag('debug', @_ == 3 ? ($_[2]+1) : 1) & $_[1]; } # # ->check_trace # # return true when the given mask matches the flag setting for trace # mode # # Arguments: # $mask: bit field that is compared to the setting. # # $caller_penalty: [optional] allows to provide a penalty used to # determine the function features (via caller()) that is used to get # the configuration setting. When not specified or 0, the call level # right above the function that call the check_trace (2 steps from # here) will be used. # # Returns: # a boolean value. # sub check_trace { return $_[0]->flag('trace', @_ == 3 ? ($_[2]+1) : 1) & $_[1]; } # # ->flag # # Perform a walkthrough the different level of configuration setting # and and gets a (additive | replacing) value for the result computation. # # When requesting the flag for 'debug' or 'trace', each stage value is # added. For 'args' request, each value overwrites the previous one. # # The walkthrough is perfomed in the following order: # - default # - file # - routine # - routine for file # - type # - routine for type # # Arguments: # $field: string that indicates the key that is used during the # walkthrough. It is either 'debug', 'trace' or 'args'. # # $caller_penalty: [optional] allows to provide a penalty used to # determine the function features (via caller()) that is used to get # the configuration setting. When not specified or 0, the call level # right above the function that call the check_trace (2 steps from # here) will be used. # # Returns: # a value that depends from the $field request: # for 'debug' and 'trace': it represents a bit field. # for 'args': it is an integer.. # sub flag { my $self = shift; my ($field, $caller_penalty) = @_; # get debug caller (for filename location) my $caller_level = defined $caller_penalty ? (1 + $caller_penalty) : 1; my ($package, $filename, $line1) = caller($caller_level); # get debug caller (for routine name) package DB; use vars qw(@args); # ignore warning my ($package1, $filename1, $line, $subroutine, $hasargs, $wantarray, $evaltext, $is_require) = caller($caller_level + 1); package Carp::Datum::Cfg; # the method that is gonna used to compute the different flag # depends of what it is looked for: # 'debug' or 'trace' -> flags are merged during the walkthrough # 'args' -> value are overwritten during the walkthough my $merge_routine = \&add_flag; $merge_routine = \&add_args if $field eq 'args'; $subroutine = '' unless defined $subroutine; my ($func_name) = $subroutine =~ /.*::(\S+)/; my $file_routine = undef; my $type_routine = undef; # first get the default flag setting my $result = &$merge_routine(0, $self->cfg_table->{default}->{$field}); # update with cluster setting my $cluster_cfg = $self->{cfg_cluster}; if (defined $cluster_cfg) { # perhaps, the package gets directly an entry in the table if (defined $cluster_cfg->{$package}) { $result = &$merge_routine( $result, $cluster_cfg->{$package}->{flags}->{$field} ); } else { # anyway, try to find a filter matching a part of the package name my $tmp = $package; while ($tmp =~ /(.*)::/) { $tmp = $1; if (defined $cluster_cfg->{$tmp}) { $result = &$merge_routine( $result, $cluster_cfg->{$tmp}->{flags}->{$field} ); last; } }; } } # update with file specific setting (if any), trying base name second my $file_cfg = $self->{cfg_file}->{$filename}; if (defined $file_cfg) { $result = &$merge_routine($result, $file_cfg->{flags}->{$field}); $file_routine = $file_cfg->{routine}->{$func_name}; } else { $file_cfg = $self->{cfg_file}->{basename($filename)}; if (defined $file_cfg) { $result = &$merge_routine($result, $file_cfg->{flags}->{$field}); $file_routine = $file_cfg->{routine}->{$func_name}; } } # update with routine specific setting (if any) my $routine_cfg = $self->{cfg_routine}->{$func_name}; $result = &$merge_routine($result, $routine_cfg->{flags}->{$field}); # update with routine specific setting from file specification (if any) $result = &$merge_routine($result, $file_routine->{flags}->{$field}); # update with dynamic type specific setting (if any) my $dyna_type = ''; ($dyna_type) = $DB::args[0] =~ /(.*)=\w+\(.*\)/ if defined $DB::args[0]; my $dyna_cfg = $self->{cfg_type}->{$dyna_type}; $result = &$merge_routine($result, $dyna_cfg->{flags}->{$field}); # update with routine specific setting from type specification (if any) $type_routine = $dyna_cfg->{routine}->{$func_name}; $result = &$merge_routine($result, $type_routine->{flags}->{$field}); return $result; } 1; =head1 NAME Carp::Datum::Cfg - Dynamic Debug Configuration Setting for Datum =head1 SYNOPSIS # In application's main use Carp::Datum qw(:all on); # turns Datum "on" or "off" DLOAD_CONFIG(-file => "./debug.cf", -config => "config string"); =head1 DESCRIPTION By using the DLOAD_CONFIG function in an application's main file, a debugging configuration can be dynamically loaded to define a particular level of debug/trace flags for a specific sub-part of code. For instance, the tracing can be turned off when entering a routine of a designated package. That is very useful for concentrating the debugging onto the area that is presently developed and/or to filter some verbose parts of code (recursive function call), when they don't need to be monitored to fix the problem. =head1 EXAMPLE Before the obscure explaination of the grammar, here is an example of what can be specified by dynamic configuration: /* * flags definition: macro that can be used in further configuration * settings */ flags common { all(yes); trace(yes): all; } flags silent { all(yes); flow(no); trace(no); return(no); } /* * default setting to use when there is no specific setting * for the area */ default common; /* * specific settings for specific areas */ routine "context", "cleanup" { use silent; } routine "validate", "is_num", "is_greater" { use silent; } file "Keyed_Tree.pm" { use silent; } file "Color.pm" { use silent; trace(yes): emergency, alert, critical; } cluster "CGI::MxScreen" { use silent; assert(no); ensure(no); } /* * aliasing to reduce the trace output line length */ alias "/home/dehaudtc/usr/perl/lib/site_perl/5.6.0/CGI" => ""; =head1 INTERFACE The only user interface is the C routine, which expects the following optional named parameters: =over 4 =item C<-config> => I Give an inlined configuration string that is appended to the one defined by C<-file>, if any. =item C<-file> => I Specifies the configuration file to load to initialize the debugging and tracing flags to be used for this run. =back =head1 CONFIGURATION DIRECTIVES =head2 Main Configuration Directives The following main directives can appear at a nesting level of 0. The syntax unit known as I is a list of semi-colon terminated directives held within curly braces. =over 4 =item C I => I Defines an alias to be used during tracing. The I string is replaced by the I in the logs. For instance, given: alias "/home/dehaudtc/lib/CGI" => ""; then a trace for file C would be traced as coming from file CCGIE/Carp.pm>, which is nicer to read. =item C I, I I The I defines the flags to be applied to all named clusters. A cluster is a set of classes under a given name scope. Cluster names are given by strings within double quotes, as in: cluster "CGI::MxScreen", "Net::MsgLink" { use silent; } This would apply to all classes under the "CGI::MxScreen" or "Net::MsgLink" name scopes, i.e. C would be affected. An exact match is attempted first, i.e. saying: cluster "CGI::MxScreen" { use verbose; } cluster "CGI::MxScreen::Screen" { use silent; } would apply the I flags for C but the I ones to C. =item C I|I. Specifies the default flags that should apply. The default flags can be given by providing the I of flags, defined by the C directive, or by expansing them in the following I. For instance: default silent; would say that the flags to apply by default are the ones defined by an earlier C directive. Not expanding defaults allows for quick switching by replacing I with I. It is up to the module user to define what is meant by that though. =item C I, I I The I defines the flags to be applied to all named files. File names are given by strings withing double quotes, as in: file "foo.pm", "bar.pm" { use silent; } This would apply to all files named "foo.pm" or "bar.pm", whatever their directory, i.e. it would apply to C as well as C<../bar.pm>. An exact match is attempted first, i.e. saying: file "foo.pm" { use verbose; } file "/tmp/foo.pm" { use silent; } would apply the I flags for C but the I ones to C<./foo.pm>. =item C I I Define a symbol I whose flags are described by the following I. This I can then be used in C and C directives. For instance: flags common { all(yes); trace(yes): all; } would define the flags known as I, which can then be re-used, as in: flags other { use common; # reuses definiton of common flags panic(no); # but switches off panic, enabled in common } A flag symbol must be defined prior being used. =item C I, I I The I defines the flags to be applied to all named routines. Routine names are given by strings within double quotes, as in: routine "foo", "bar" { use silent; } This would apply to all routines named "foo" or "bar", whatever their package, for instance C and C. =head2 Debugging and Tracing Flags Debugging (and tracing) flags can be specified only within syntactic I items, as expected by main directives such as C or C. Following is a list of debugging flags that can be specified in the configuration. The order in which they are given in the file is significant: the I/I settings are applied sequentially. =over 4 =item C I Uses flags defined by a C directive under I. It acts as a recursive macro expansion (since C can also be specified in C). The symbol I must have been defined earlier. =item flow(yes|no) Whether to print out the entering/exiting of routines. That implies the invocation of the C function in the routines. =item return(yes|no) Whether to print out the returned when using the return C and C routines. =item trace(yes|no) Whether to print out traces specified by the C function. By default all trace levels are affected. It may be followed by a list of trace levels affected by the directive, as in. trace(yes): emergency, alert, critical; Trace levels are purely conventional, and have a strict one-to-one mapping with C levels given at the C call. They are further described in L below. There is one bit per defined trace level, contrary to the convention established by syslog(), for better tuning. =item require(yes|no) Whether to evaluate the pre-condition given by C. But see L below. =item assert(yes|no) Whether to evaluate the assertion given by C. But see L below. =item ensure(yes|no) Whether to evaluate the post-condition given by C. But see L below. =item panic(yes|no) Whether to panic upon an assertion failure (pre/post condition or assertion). If not enabled, a simple warning is issued, tracing the assertion failure. =item stack(yes|no) Whether to print out a stack trace upon assertion failure. =item all(yes|no) Enable or disables B the previously described items. =back =head2 Assertion Evaluation Note When C is switched off, the assertions are always monitored, and any failure is fatal. This is because a failing assertion is a Bad Thing in production mode. Also, since C and friends are not C macros but routines, the assertion expression is evaluated anyway, so it might as well be tested. Therefore, a directive like: require(no); will only turn off monitoring of pre-conditions in debugging mode (e.g. because the interface is not finalized, or the clients do not behave properly yet). =head2 Trace Levels Here is the list of trace flags that can be specified by the configuration: Configuration DTRACE flag ------------- ------------- all TRC_ALL emergency TRC_EMERGENCY alert TRC_ALERT critical TRC_CRITICAL error TRC_ERROR warning TRC_WARNING notice TRC_NOTICE info TRC_INFO debug TRC_DEBUG A user could say something like: trace(no): all; trace(yes): emergency, alert, critical, error; Since flags are applied in sequence, the first directive turns all tracing flags to off, the second enables only the listed ones. =head1 BUGS Some things are not fully documented. =head1 AUTHORS Christophe Dehaudt and Raphael Manfredi are the original authors. Send bug reports, hints, tips, suggestions to Dave Hoover at . =head1 SEE ALSO Log::Agent(3). =cut