views:

110

answers:

4

If I have a subroutine that opens a file what is the best way to ensure it opens it only upon the first time the subrountine is called? I have this but not sure if its best practice:

{
my $count = 0;
sub log_msg {
    my ($msg,$name) = @_;

    if ($count == 0) {
        my $log_file_name = "/tmp/" . $name;
        open my $log_fh,">",$log_file_name or  croak "couldn't open $log_file_name : $!";
        print $log_fh "$timestamp: created and opened $log_file_name\n";
    }
    $count++;
    }
}
A: 

Well, for starters, the $count++ should go inside your if statement and can be changed to simply $count=1. You may also want to rename $count to $file_opened_flag or something more meaningful, as well. Other than that, I see nothing wrong with this.

Michael Goldshteyn
there are other things to depend on $count that I couldn't publish here..
ennuikiller
I see. Well, hopefully the rest of my answer helps to answer your question.
Michael Goldshteyn
yes Michael it does, thank you!
ennuikiller
+8  A: 

Sounds like a good reason to use a state variable. Store the filehandles in a persistent hash.

#!/usr/bin/perl

use 5.010;
use strict;
use warnings;

sub log_msg {
  state %fh;
  my ($msg, $name) = @_;

  unless ($fh{$name}) {
    warn "Opening $name\n";
    open $fh{$name}, '>', $name or die $!;
    print {$fh{$name}} scalar localtime, " Opened file\n";
  }

  print {$fh{$name}} $msg, "\n";
}

log_msg('Message1', 'first.log');
log_msg('Message2', 'first.log');
log_msg('MessageA', 'second.log');
log_msg('MessageB', 'second.log');

Note the extra set of braces around the filehandles in the print call. That's because print is a bit picky about what you can use as its filehandle argument.

davorg
I like this....thanks!
ennuikiller
If you need compatibility with 5.8 (which doesn't have `state` variables), you can use a `my` var outside the sub instead (just like `$count` in ennuikiller's example).
cjm
`use feature "state";` should do the trick in 5.8 and beyond.
slu
I show a per-5.10 example, but I wouldn't use state() here anyway since I'd refactor this into a sub that prints the log message and one that keeps track of the filehandles.
brian d foy
A: 

I don't think there is anything wrong with using per se but if there is really only 1 file to track, why not just keep $log_fh in the closure and use if(!$log_fh->opened()) instead of a count variable?

frankc
I want to allow the caller to pass a name for the log file ...
ennuikiller
+3  A: 

The best way is to use Log::Log4perl so you don't have to think about it and you can focus on your real task.

Aside from that, you can use some of the tricks for files and filehandles that we cover in Effective Perl Programming. Luckily for you, that's also the free chapter that our publisher gives away.

In short, you don't want to think about that in your logging routine. It's cluttercode. Instead, create a method that either returns the cached filehandle or opens it (this is a lot like a method you'd use to ping a database handle and reconnect if needed):

 sub log_msg {
      my( $self, $msg, $name ) = @_;

      print { $self->get_fh_by_name( $name ) } $msg;
      }

 BEGIN { # to define variables before the subroutine
 my %log_fhs;
 sub get_fh_by_name {
     my( $self, $name ) = @_;

     return $log_fhs{$name} if defined $log_fhs{$name};

     open my $log_fh, catdir( $base_dir, $name ) or croak "...";
     print $logfh ...

     $log_fhs{$name} = $log_fh;
     }
 }
brian d foy
I bought the book, both print and electronic .... love it!!
ennuikiller