Beefy Boxes and Bandwidth Generously Provided by pair Networks
go ahead... be a heretic
 
PerlMonks  

Confusion about properly using Carp

by jeffa (Bishop)
on Mar 02, 2002 at 15:19 UTC ( [id://148823]=perlquestion: print w/replies, xml ) Need Help??

jeffa has asked for the wisdom of the Perl Monks concerning the following question:

I have a module that is effectively a wrapper for DBI - when you call it's constructor (new), you pass the same arguments you would to DBI::connect(). The problem i am having is getting the module to carp errors back to the client without giving away the line number from the module itself. For example, given some wrapper named Foo and trying to connect to a non-existant server, i would like for the following error:
DBI->connect(mysql:host) failed: Unknown MySQL Server Host 'host' (2)
   at Foo.pm line 13
   at ./foo.pl line 11
to only list ./foo.pl, the client that called the module.

Now, philosophical question: if this really should be desired behavior and left alone, then i am all for that. But, if there is a way to implement what i'd like, then i would sure appreciate a nudge in the right direction. My reasoning is that such errors should not accidentaly lead a user into the source code of the module, because it is a 'red herring'. By even mentioning the originating line of code from the wrapper module, a user might just be tempted to think the 'module is broken.' (I also have an appreciation for debugging as well, and would like to leave that door open.)

Here is what i have been working with so far:

package Foo; use DBI; use Carp; use strict; sub new { my $class = shift; my $self = {}; bless $self, $class; # THE PROBLEM: $self->{'dbh'} = DBI->connect(@_) or carp; return $self; } 1;
I did try wrapping the instantiation in an eval block:
# THE SAME PROBLEM: eval { $self->{'dbh'} = DBI->connect(@_,{RaiseError=>1,PrintError=>0}) }; carp $@ if $@;
... but the results were exactly the same. An evil thought did cross my mind at this point - substitute the module line number(s) out ... but that surely is not 'The Right Way'.

And here is the client which calls the wrapper with bad credentials to generate the errors. It also uses DBI in the same manner as the 'control subject':

use strict; use Foo; use DBI; print "Comparing wrapper:\n"; my $foo = Foo->new(qw(DBI:mysql:mysql:host user pass)); print "\nComparing DBI:\n"; my $dbh = DBI->connect(qw(DBI:mysql:mysql:host user pass));
What i would like is to only show that the error orginated from the client and not the module ... just like DBI reports:
DBI->connect(mysql:host) failed: Unknown MySQL Server Host 'host' (2)
   at ./foo.pl line 14
Thanks!

jeffa

L-LL-L--L-LL-L--L-LL-L--
-R--R-RR-R--R-RR-R--R-RR
B--B--B--B--B--B--B--B--
H---H---H---H---H---H---
(the triplet paradiddle with high-hat)

Replies are listed 'Best First'.
Re (tilly) 1: Confusion about properly using Carp
by tilly (Archbishop) on Mar 02, 2002 at 19:35 UTC
    When Perl 5.8 comes out you will be able to control this behaviour through use of @CARP_NOT.

    In the meantime the only solutions are to either replace the offending functions with your own (in general a bad idea) or be sure that you inherit from all classes that errors might come from.

    Some explanation might be in order.

    The reason is that Carp's way of finding where to report an error from is to walk the stack, looking for the first "untrusted" call. How is that determined? Well any call from a package to itself is trusted. Any call to or from a subclass is trusted. (OK, in 5.005 and earlier the implementation used a hack that didn't always do that.) Calls to and from Carp are trusted. (OK, that is the new implementation. The old one just arranged to have 2 call levels, and used goto liberally to keep it so. Blech.)

    So in effect, there is a trust relationship built transitively out of whatever was in @ISA. Therefore the only way to control what would be reported where is by arranging to inherit from the class you don't want to report errors from. The change in 5.8 is that if you supply @CARP_NOT, Carp uses that for establishing the trust relationship instead of @ISA. (Because inheritance and "new code" are separate concepts.)

Re: Confusion about properly using Carp
by koolade (Pilgrim) on Mar 02, 2002 at 17:03 UTC

    Now, philosophical question: if this really should be desired behavior and left alone, then i am all for that.

    Carp may not really be what you need here. If your error messages are intended to be seen by programmers (i.e., you when you're debugging) then you should leave the entire trace so you (or a maintainer) don't go crazy looking for an error in foo.pl when it really originates in Foo.pm. Besides, it'll be a pain trying to figure out the correct $CarpLevel value if you have metethods within Foo.pm call other methods who call other methods...that eventually generate the error.

    If your error messages are intended toward the foo.pl user, who may not be a Perl programmer, then Carp's output will really going to confuse them no matter what the trace is. The user really only needs to know the error message, not what line the error occurred on. I'd suggest a cleaner output of error messages for them, holding their hand, and telling them that everything's going to be OK--not spitting out line numbers and script names.

    BTW, if you're doing this with a CGI script, check out Ovid's warnings on using CGI::Carp at http://www.easystreet.com/~ovid/cgi_course/lesson_three/lesson_three.html.

      Let me add to this since I mostly agree. I personally distinguish between 3 types of diagnostics: (1) The user level errors koolade describes above; (2) errors given to prgrammers using a given published API; and (3) full programmer level diagnostics.

      I'd agree with koolade's approach, but that only covers the first and third cases. For an applications programmer using some interface I developed, I want to give them that middle ground: Tell them which line of their program is in error but not lead them into a bunch of code they're never supposed to see. So that's where I've used things like $CarpLevel (even though it can be a pain in the arse).

      As an example, I wrote a generalized interface for setting up class and instance level attributes. Part of that checks against allowed values, etc. If a programmer calls a method with invalid arguments I simply want to say something like Option FOO is not recognized at line 120 of yourprogram. That's different than a user giving me a bad command line argument, where I'd simply say something like Option -foo is not recognized.. I would not want to show the user in that first message a stack trace though. My experience, like jeffa's is that it just confuses things and they start thinking things are broken that aren't. I would show a stack trace for exceptions that were unexpected; e.g., errors in my underlying code versus errors in usage of that code.

Re: Confusion about properly using Carp
by steves (Curate) on Mar 02, 2002 at 16:35 UTC

    Have you fully checked the Carp code? It's been a while but I seem to rememember a short and long message format, the short being the default; the long giving a full stack trace. The code counts all sub-classes in a calling path as one "level", so if you were to derive your module from DBI in this instance you'd get what you want. Then there's a $CarpLevel scalar you can set to push it back further in cases like yours. I've used that successfully; always by setting a local value before calling carp(). The trick is knowing how far back your client is. In the few cases I had to do this I either knew or could figure it out using caller().

      Please don't use $Carp::CarpLevel. Ever.

      I have almost never seen it used anything like correctly, even when it was used inside of the core Perl distribution.

      If you think you have used it correctly, then you are almost definitely wrong unless you can tell me that you know that it works differently on a croak and a confess and can tell me what the difference is. (Hint: It works differently on croak between 5.005 and 5.6. Are you still sure you used it correctly?)

        I disagree. If you know where you are in a calling stack, this has worked for me numerous times:

        { local $Carp::CarpLevel = 2; carp "Some error"; }

        No need to worry about croak or confess here that I can see. What am I missing?

Re: Confusion about properly using Carp
by Juerd (Abbot) on Mar 02, 2002 at 16:55 UTC
    The backtrace is created by Carp's croak() or carp() (croak with RaiseError, carp with PrintError).
    You can override Carp's functions in the DBI namespace as follows:
    *DBI::croak = sub { ... };

    Lbh ebgngrq guvf grkg naq abj lbh pna ernq vg. Fb jung? :) -- Whreq

      He's calling carp() from Foo.pm, not DBI.pm, so this won't accomplish what he's asking.
        I was thinking along the lines of: (warning: untested code ahead)
        package Foo; ... our $error; *DBI::carp = sub { $Foo::error = shift }; ... my $self->{dbh} = DBI->connect( ... ) or carp $error;

        Lbh ebgngrq guvf grkg naq abj lbh pna ernq vg. Fb jung? :) -- Whreq

Re: Confusion about properly using Carp
by youwin (Beadle) on Jul 12, 2011 at 19:52 UTC
    This question still hasn't been answered. I would like to do the same:
    package A; use DBI; @CARP_NOT = "DBI"; # doesn't work sub foo { my $dbh = DBI->connect(..., {RaiseError => 1}); my $sth = $dbh->prepare("asdf"); $sth->execute; # bang! }
    I would like the following to report the error from the caller, but I cant find the right way to do it.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://148823]
Approved by root
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others browsing the Monastery: (5)
As of 2024-03-29 08:41 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found