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

Hi,

I have 2 questions.

1) Avoid warning message

use strict; use warnings; my $logfile = "test.log"; my $log_fh = *LOG_FH; LOG_MSG_OPEN($log_fh,$logfile); ... sub LOG_MSG_OPEN { my $par_fh = $_[0]; open($par_fh,"> $par_filepath") or die ("Can't open $par_filepath: + $!\n"); $par_fh->autoflush(1); }

gives me this error message: Name "main::LOG_FH" used only once: possible typo at...

Is the only way to get rid of the mssage to add

#no warnings 'once';

?

2) Create FILEHANDLE name from a variable

my $logfile = "test.log"; my $log_fh = "LOG_FH"; LOG_MSG_OPEN($log_fh,$logfile); ... sub LOG_MSG_OPEN { my $par_fh = $_[0]; my $par_filepath = $_[1]; my $par_fh_2 = *${par_fh}; open($par_fh_2,"> $par_filepath") or die ("Can't open $par_filepat +h: $!\n"); $par_fh_2->autoflush(1); }

gives me this error message: Can't use string ("LOG_FH") as a symbol ref while "strict refs" in use..

Is it possible to create FILEHANDLEs with a variable

Thanks for help.

Regards, de Michi

Replies are listed 'Best First'.
Re: File Handle questions
by kcott (Archbishop) on Oct 23, 2016 at 14:09 UTC

    G'day demichi,

    Declaring LOG_FH should, I think, fix both your posted problems.

    You'll then find another problem with your $log_fh assignment. You need to assign a globref:

    #my $log_fh = *LOG_FH; my $log_fh = \*LOG_FH;

    To be honest, I don't know why you're even bothering with that globally-scoped package variable. Why not just:

    my $log_fh; LOG_MSG_OPEN($log_fh,$logfile);

    Recomendations:

    • Names with all uppercase characters are typically reserved for constants, filehandles, and so on. Using them for other purposes (e.g. LOG_MSG_OPEN in your post) can be confusing and error-prone.
    • Use the 3-argument form of open.
    • Consider using the autodie pragma.

    Update: Expanded the "Names with all uppercase characters ..." point.

    — Ken

      Hi,

      this

      my $log_fh = \*LOG_FH;

      does not help as I get the same errors. But returning the FH in the sub works :)

      my $log_fh; # = \*LOG_FH; $log_fh = LOG_MSG_OPEN($log_fh,$logfile); LOG_MSG($log_fh,$vbse,"v",$lglvl,5,"GENERAL","Starting Script $0"); ... module ... sub LOG_MSG_OPEN { my $par_fh = $_[0]; my $par_filepath = $_[1]; open($par_fh,"> $par_filepath") or die ("Can't open $par_filepath: + $!\n"); $par_fh->autoflush(1); return *$par_fh; } sub LOG_MSG { my $par_fh = shift (@_); ... print $par_fh "$logdate,$logtime,$SEV_KEYWORD,$par_FUNCTION,@line\ +n"; }

      Thanks for your other recommendations. I always used uppercase fos my subs. I will check the open and autodie. Kind regards, de Michi

        "... my $log_fh = \*LOG_FH; does not help as I get the same errors."

        I suggested declaring LOG_FH to "fix both your posted problems".

        Using the globref, \*LOG_FH, was to handle an anticipated, subsequent problem.

        — Ken

      It looks like autodie is a very good idea. Can you give me an example how I can use my function (for logging) with autodie as I do it with die?

      With die:

      unlink $filename or die LOG_MSG($par_lglvl,3,"DOUPGRADE","Could not de +lete $filename: $!");
      Thanks.
        $ cat ad.pl #!/usr/bin/env perl use strict; use warnings; use autodie; unlink ('filewhichdoesnotexist'); print "Everything went fine.\n"; $ ./ad.pl Can't unlink('filewhichdoesnotexist'): No such file or directory at ./ +ad.pl line 6 $

        So you can simply redirect STDERR to the logfile of your choosing.

        Looking at autodie, it seems to not be easy to use with logging - other than redirecting STDERR in to your log file. Also, it doesn't seem to have a hook for providing a function for custom formatting.

        You could try using an END block to log the error after something dies.

        You could set up a $SIG{__DIE_} handler (see %SIG) to intercept the error and call your log function. HOWEVER, $SIG{__DIE_} handlers are called even when die is called inside an eval, so can be messy to get working correctly.

        Maybe overriding CORE::GLOBAL::die() would work for your purpose.

        You could follow the examples in autodie to "trap" the die with eval, then examine $@ to determine whether and what to log.

        In your case, I'd say either redirect STDERR to your log file, or use die LOG_MSG(...); the way you are, now.

Re: File Handle questions
by stevieb (Canon) on Oct 23, 2016 at 13:00 UTC

    It's a little difficult to understand what you need here. Could you provide some detail as to what it is you're trying to achieve and why? A bit of context around the issue you're trying to solve will help us provide you with ways to get where you want to go.

      Sure, I would like to have my log handling in a module that does all my logging stuff as I neede it. Therefoe I have 3 subs in this module ... LOG_OPEN, LOG_MSG, LOG_CLOSE. I need to give the function LOG_OPEN a filename and a filehandle (that I am flexible to have more than 1 log file in a script). Now I stuck with the file handle as described. I hope this helps.

        I'd take a bit of a different approach, and have the logging module create a default log handle internally, with a log() function that takes a message and an optional filename. If the optional filename is sent in, we create a new handle internally and log to it, otherwise we log to the default handle. Here's some code I put together very hastily as an example:

        use warnings; use strict; package Log; { sub new { my $self = bless {}, shift; my $log_file = shift; open my $fh, '>', $log_file or die $!; $self->{default_log} = $fh; return $self; } sub log { my ($self, $msg, $log) = @_; my $log_fh; if (defined $log){ # filename sent in open my $fh, '>>', $log or die $!; $log_fh = $fh; } else { # use the default fh $log_fh = $self->{default_log}; } print $log_fh "$msg\n"; } } package main; { my $log = Log->new('default.log'); $log->log('default log message'); $log->log('alternate log file msg', 'not-default.log'); }

        Default log file:

        $ cat default.log default log message

        Alternate log file:

        $ cat non-default.log alternate log file msg

        Is that kind of the idea you're after? Something along these lines prevents the user of the module of having to create and send in their own handles. If I'm off-base, just let us know.

        update: If you want the user to be able to send in their own file handle instead of just a file name, the log() method could be changed like this:

        sub log { my ($self, $msg, $log) = @_; my $log_fh; if (defined $log && ref $log ne 'GLOB'){ # filename sent in open my $fh, '>>', $log or die $!; $log_fh = $fh; } elsif (defined $log && ref $log eq 'GLOB'){ # file handle sent in $log_fh = $log; } else { # use the default fh $log_fh = $self->{default_log}; }

        ...and called like this (continuing on with the main example above):

        open my $fh, '>>', 'alternate.log' or die $!; $log->log('msg', $fh);

        /update

        My recommendation would be to not bother with writing your own logging subs, but instead to use one of the already written logging modules on cpan. My first choice would be Log::Log4perl