Author: darnold
Date: Tue Jan 9 11:02:28 2007
New Revision: 8532
Modified:
dbi/trunk/Changes
dbi/trunk/DBI.pm
dbi/trunk/DBI.xs
dbi/trunk/DBIXS.h
dbi/trunk/lib/DBI/PurePerl.pm
Log:
added support for filehandle traces
Modified: dbi/trunk/Changes
==============================================================================
--- dbi/trunk/Changes (original)
+++ dbi/trunk/Changes Tue Jan 9 11:02:28 2007
@@ -16,6 +16,7 @@
Updated DBI::DBD docs for driver authors thanks to Ammon Riley.
Added SQL_BIGINT type code (resolved to the ODBC/JDBC value (-5))
+ Updated trace to support filehandle argument.
=head2 Changes in DBI 1.53 (svn rev 7995), 31st October 2006
Modified: dbi/trunk/DBI.pm
==============================================================================
--- dbi/trunk/DBI.pm (original)
+++ dbi/trunk/DBI.pm Tue Jan 9 11:02:28 2007
@@ -2833,6 +2833,7 @@
DBI->trace($trace_setting)
DBI->trace($trace_setting, $trace_filename)
+ DBI->trace($trace_setting, $trace_filehandle)
$trace_setting = DBI->trace;
The C<DBI-E<gt>trace> method sets the I<global default> trace
@@ -7052,18 +7053,145 @@
Initially trace output is written to C<STDERR>. Both the
C<$h-E<gt>trace> and C<DBI-E<gt>trace> methods take an optional
-$trace_filename parameter. If specified, and can be opened in
-append mode, then I<all> trace output (currently including that
-from other handles) is redirected to that file. A warning is
-generated if the file can't be opened.
-
-Further calls to trace() without a $trace_filename do not alter where
-the trace output is sent. If $trace_filename is undefined, then
-trace output is sent to C<STDERR> and the previous trace file is closed.
-
-Currently $trace_filename can't be a filehandle. But meanwhile you
-can use the special strings C<"STDERR"> and C<"STDOUT"> to select
-those filehandles.
+$trace_file parameter, which may be either the name of a file to be
+openned by DBI in append mode, or a reference to an existing writable
+(possibly layered) filehandle. If $trace_file is a filename,
+and can be opened in append mode, or $trace_file is a writable
+filehandle, then I<all> trace output (currently including that from
+other handles) is redirected to that file. A warning is generated
+if $trace_file can't be opened or is not writable.
+
+Further calls to trace() without $trace_file do not alter where
+the trace output is sent. If $trace_file is undefined, then
+trace output is sent to C<STDERR> and, if the prior trace was openned with
+$trace_file as a filename, the previous trace file is closed; if $trace_file
was
+a filehandle, the filehandle is B<not> closed.
+
+B<NOTE>: If $trace_file is specified as a filehandle, the filehandle
+should not be closed until all DBI operations are completed, or the
+application has reset the trace file via another call to
+C<trace()> that changes the trace file.
+
+=head2 Tracing to Layered Filehandles
+
+B<NOTE>:
+
+=over 4
+
+=item *
+Tied filehandles are not currently supported, as
+tie operations are not available to the PerlIO
+methods used by the DBI.
+
+=item *
+PerlIO layer support requires Perl version 5.8 or higher.
+
+=back
+
+As of version 5.8, Perl provides the ability to layer various
+"disciplines" on an open filehandle via the L<PerlIO> module.
+
+A simple example of using PerlIO layers is to use a scalar as the output:
+
+ my $scalar = '';
+ open( my $fh, "+>:scalar", \$scalar );
+ $dbh->trace( 2, $fh );
+
+Now all trace output is simply appended to $scalar.
+
+A more complex application of tracing to a layered filehandle is the
+use of a custom layer (I<Refer to >L<Perlio::via> I<for details
+on creating custom PerlIO layers.>). Consider an application with the
+following logger module:
+
+ package MyFancyLogger;
+
+ sub new
+ {
+ my $self = {};
+ my $fh;
+ open $fh, '>', 'fancylog.log';
+ $self->{_fh} = $fh;
+ $self->{_buf} = '';
+ return bless $self, shift;
+ }
+
+ sub log
+ {
+ my $self = shift;
+ return unless exists $self->{_fh};
+ my $fh = $self->{_fh};
+ $self->{_buf} .= shift;
+ #
+ # DBI feeds us pieces at a time, so accumulate a complete line
+ # before outputing
+ #
+ print $fh "At ", scalar localtime(), ':', $self->{_buf}, "\n" and
+ $self->{_buf} = ''
+ if $self->{_buf}=~tr/\n//;
+ }
+
+ sub close {
+ my $self = shift;
+ return unless exists $self->{_fh};
+ my $fh = $self->{_fh};
+ print $fh "At ", scalar localtime(), ':', $self->{_buf}, "\n" and
+ $self->{_buf} = ''
+ if $self->{_buf};
+ close $fh;
+ delete $self->{_fh};
+ }
+
+ 1;
+
+To redirect DBI traces to this logger requires creating
+a package for the layer:
+
+ package PerlIO::via::MyFancyLogLayer;
+
+ sub PUSHED
+ {
+ my ($class,$mode,$fh) = @_;
+ my $logger;
+ return bless \$logger,$class;
+ }
+
+ sub OPEN {
+ my ($self, $path, $mode, $fh) = @_;
+ #
+ # $path is actually our logger object
+ #
+ $$self = $path;
+ return 1;
+ }
+
+ sub WRITE
+ {
+ my ($self, $buf, $fh) = @_;
+ $$self->log($buf);
+ return length($buf);
+ }
+
+ sub CLOSE {
+ my $self = shift;
+ $$self->close();
+ return 0;
+ }
+
+ 1;
+
+
+The application can then cause DBI traces to be routed to the
+logger using
+
+ use PerlIO::via::MyFancyLogLayer;
+
+ open my $fh, '>:via(MyFancyLogLayer)', MyFancyLogger->new();
+
+ $dbh->trace('SQL', $fh);
+
+Now all trace output will be processed by MyFancyLogger's
+log() method.
=head2 Trace Content
Modified: dbi/trunk/DBI.xs
==============================================================================
--- dbi/trunk/DBI.xs (original)
+++ dbi/trunk/DBI.xs Tue Jan 9 11:02:28 2007
@@ -611,6 +611,22 @@
return 1;
}
+static void
+close_trace_file()
+{
+ dTHX;
+ dPERINTERP;
+ if (DBILOGFP == PerlIO_stderr() || DBILOGFP == PerlIO_stdout())
+ return;
+
+ if (DBIS->logfp_ref == NULL)
+ PerlIO_close(DBILOGFP);
+ else {
+ /* DAA dec refcount and discard */
+ SvREFCNT_dec(DBIS->logfp_ref);
+ DBIS->logfp_ref = NULL;
+ }
+}
static int
set_trace_file(SV *file)
@@ -619,41 +635,51 @@
dPERINTERP;
STRLEN lna;
const char *filename;
- PerlIO *fp;
+ PerlIO *fp = Nullfp;
+ IO *io;
+
if (!file) /* no arg == no change */
return 0;
- /* XXX need to support file being a filehandle object */
- filename = (SvOK(file)) ? SvPV(file, lna) : Nullch;
- /* undef arg == reset back to stderr */
- if (!filename || strEQ(filename,"STDERR")) {
- if (DBILOGFP != PerlIO_stderr() && DBILOGFP != PerlIO_stdout())
- PerlIO_close(DBILOGFP);
- DBILOGFP = PerlIO_stderr();
- return 1;
- }
- if (strEQ(filename,"STDOUT")) {
- if (DBILOGFP != PerlIO_stderr() && DBILOGFP != PerlIO_stdout())
- PerlIO_close(DBILOGFP);
- DBILOGFP = PerlIO_stdout();
- return 1;
- }
- fp = PerlIO_open(filename, "a+");
- if (fp == Nullfp) {
- warn("Can't open trace file %s: %s", filename, Strerror(errno));
- return 0;
+
+ /* DAA check for a filehandle */
+ if (SvROK(file)) {
+ io = sv_2io(file);
+ if (!io || !(fp = IoOFP(io))) {
+ warn("DBI trace filehandle is not valid");
+ return 0;
+ }
+ close_trace_file();
+ SvREFCNT_inc(io);
+ DBIS->logfp_ref = io;
}
else {
- if (DBILOGFP != PerlIO_stderr())
- PerlIO_close(DBILOGFP);
- DBILOGFP = fp;
- /* if this line causes your compiler or linker to choke */
- /* then just comment it out, it's not essential. */
- PerlIO_setlinebuf(fp); /* force line buffered output */
- return 1;
- }
+ filename = (SvOK(file)) ? SvPV(file, lna) : Nullch;
+ /* undef arg == reset back to stderr */
+ if (!filename || strEQ(filename,"STDERR")) {
+ close_trace_file();
+ DBILOGFP = PerlIO_stderr();
+ return 1;
+ }
+ if (strEQ(filename,"STDOUT")) {
+ close_trace_file();
+ DBILOGFP = PerlIO_stdout();
+ return 1;
+ }
+ fp = PerlIO_open(filename, "a+");
+ if (fp == Nullfp) {
+ warn("Can't open trace file %s: %s", filename, Strerror(errno));
+ return 0;
+ }
+ close_trace_file();
+ }
+ DBILOGFP = fp;
+ PerlIO_printf(DBILOGFP," Trace file set\n");
+ /* if this line causes your compiler or linker to choke */
+ /* then just comment it out, it's not essential. */
+ PerlIO_setlinebuf(fp); /* force line buffered output */
+ return 1;
}
-
static IV
parse_trace_flags(SV *h, SV *level_sv, IV old_level)
{
Modified: dbi/trunk/DBIXS.h
==============================================================================
--- dbi/trunk/DBIXS.h (original)
+++ dbi/trunk/DBIXS.h Tue Jan 9 11:02:28 2007
@@ -399,6 +399,7 @@
I32 debug;
PerlIO *logfp;
+ IO *logfp_ref; /* DAA keep ptr to filehandle for refcounting */
/* pointers to DBI functions which the DBD's will want to use */
char * (*neat_svpv) _((SV *sv, STRLEN maxlen));
@@ -487,7 +488,7 @@
(DBD_ATTRIB_OK(attribs) \
? hv_fetch((HV*)SvRV(attribs), key,klen, 0) \
: (SV **)Nullsv)
-
+
#define DBD_ATTRIB_GET_IV(attribs, key,klen, svp, var) \
if ((svp=DBD_ATTRIB_GET_SVP(attribs, key,klen)) != NULL) \
var = SvIV(*svp)
Modified: dbi/trunk/lib/DBI/PurePerl.pm
==============================================================================
--- dbi/trunk/lib/DBI/PurePerl.pm (original)
+++ dbi/trunk/lib/DBI/PurePerl.pm Tue Jan 9 11:02:28 2007
@@ -503,16 +503,41 @@
sub _set_trace_file {
my ($file) = @_;
return unless defined $file;
+ #
+ # DAA add support for filehandle inputs
+ #
+ if (ref $file eq 'GLOB') {
+ # DAA required to avoid closing a prior fh trace()
+ $DBI::tfh = undef
+ unless $DBI::tfh_needs_close;
+ $DBI::tfh = $file;
+ select((select($DBI::tfh), $| = 1)[0]);
+ $DBI::tfh_needs_close = 0;
+ return 1;
+ }
if (!$file || $file eq 'STDERR') {
+ # DAA required to avoid closing a prior fh trace()
+ $DBI::tfh = undef
+ unless $DBI::tfh_needs_close;
open $DBI::tfh, ">&STDERR" or warn "Can't dup STDERR: $!";
+ $DBI::tfh_needs_close = 1;
return 1;
}
if ($file eq 'STDOUT') {
+ # DAA required to avoid closing a prior fh trace()
+ $DBI::tfh = undef
+ unless $DBI::tfh_needs_close;
open $DBI::tfh, ">&STDOUT" or warn "Can't dup STDOUT: $!";
+ $DBI::tfh_needs_close = 1;
return 1;
}
+ # DAA required to avoid closing a prior fh trace()
+ # DAA required to avoid closing a prior fh trace()
+ $DBI::tfh = undef
+ unless $DBI::tfh_needs_close;
open $DBI::tfh, ">>$file" or carp "Can't open $file: $!";
select((select($DBI::tfh), $| = 1)[0]);
+ $DBI::tfh_needs_close = 1;
return 1;
}
sub _get_imp_data { shift->{"imp_data"}; }