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

I wrote a program a long time ago, before I discovered the virtues of strict and warnings. It was designed only for use on the command line. For nice looking output I created a subroutine Log() that accepted the string of log information and printed it to stdout with a nice looking time and added the . "\n" at the end.

Recently I converted the program to Mojolicious so it could be used by others in a web browser. I still wanted that log information to be displayed to the user, so I created a $logline variable in the main code that Log() appended to. Each route cleared it, did its thing (calling subroutines that also called Log()), and then put it in the stash to be displayed with the page. It worked very well.

The problem came when I decided it was long overdue to add strict and warnings to the program. Most of the changes I had to make were minor but I can't figure out a good way to incorporate this $logline. Many of the subroutines call other subroutines, so it seems like the only way to do this is to pass it into, and return it from, every subroutine. That seems incredibly cumbersome, modifying each subroutine and every call to it, especially since these subroutines have other data that they need to accept and pass back. I've ended up with subroutines that don't work anymore and a $logline that works sometimes and not others.

I have also tried to declare it as a global variable, but can't get that to work:

global $logLine;

error message is

Global symbol "$logLine" requires explicit package name

Is there a better way to do this? What am I missing?

Replies are listed 'Best First'.
Re: Logging to web page with Mojolicious and strict
by hippo (Archbishop) on Sep 12, 2019 at 14:43 UTC

    Probably all you need is to declare it with my at the outermost scope of your code.

    #!/usr/bin/env perl use strict; use warnings; my $logline; foo (); sub foo { $logline = 'Now we have called foo'; bar (); } sub bar { Log (); } sub Log { print "Logging: $logline\n"; }

    If this does not work for you for some reason, an SSCCE would help to pin down the problem.

      Thanks for your suggestions. Unfortunately I get the same error. Here's a SSCCE. Hopefully this helps.

      index.mojo:

      #!/usr/bin/perl use Mojolicious::Lite; my $logLine; require './bigFileFullOfSubs.pl'; require './subImWorkingOn.pl'; any '/' => sub { my $c = shift; $logLine = ""; subImWorkingOn(); $c->stash(logLine => $logLine); $c->render(template => 'main'); }; app->start; __DATA__ @@ main.html.ep <!DOCTYPE html> <html><head></head> <body> <pre> <%= $logLine %> </pre> </body> </html>
      BigFileFullOfSubs.pl:

      #!/usr/bin/perl -w use strict; use warnings; sub Log { # Generates nice looking log comment with time. my ($LogItem) = @_; $LogItem =~ s/\n/ /g; my $Time = "functionThatGetsTheTime()"; my $thisLog = "[ $Time ] :: $LogItem\n"; $logLine .= $thisLog; } return 1;
      SubImWorkingOn.pl:

      use warnings; use strict; sub subImWorkingOn { Log("Interesting information for the user here."); Log("Even more fascinating information for the user here!"); Log("Processing complete"); } return 1;
      Error message:

      Global symbol "$logLine" requires explicit package name (did you forg +et to declare "my $logLine"?) at ./bigFileFullOfSubs.pl line 11.

        As the error message suggests, your BigFileFullOfSubs.pl won't compile because you have not declared $logLine in it.

        One solution would be to share this variable among your scripts but that would get very messy very quickly. However, since you don't actually need $logLine anywhere else, just move the declaration of it out of index.mojo and into BigFileFullOfSubs.pl (outside the subroutine) and you may find this solves your problems.

        Perhaps, and I mean this with all sincerity, it's also long overdue that you convert the Perl-4-style "libraries" to real modules.

        In either case, just store the variable in the library/module that is creating it, and have it provide a subroutine to access it for the template.

Re: Logging to web page with Mojolicious and strict
by bliako (Abbot) on Sep 12, 2019 at 16:04 UTC

    Maybe declare our $logLine; in your main perl script (index.mojo) and then access the variable in each of the files you require with its fully qualified name which will be $main::logLine ?

    However, if it was me, I would create a logging class and instantiate a global log object. Which will have api like log("hello") and toString() and toHTML(). In this way I expand my logging capabilities as much as I like with just one single global variable (the log object). And when I feel like, I modify all my subs to take just one additional parameter which will be the log object. No matter how I modify the log object in the future, I will not need a second global variable and I will not need to modify my subs for a second time...

    bw, bliako

      Thanks for your suggestion. I've written a Logger package:
      package Logger; use Mojo::Log; sub new { my $class = shift; my $self = {$LogLine = '', $app = shift}; bless $self, $class; return $self; } sub add { my ( $self, $LogItem ) = @_; my $time = FormatDate(); $self->{$LogLine} .= "[ $time ] :: $LogItem\n" if defined($LogItem +); $app->log->debug($LogItem); } sub get { my( $self ) = @_; return $self->{$LogLine}; } sub clear { my( $self ) = @_; $self->{$LogLine} = ''; } sub FormatDate { my $Offset = shift; $Offset = 0 unless ($Offset); my ($Second, $Minute, $Hour, $Day, $Month, $Year, $WeekDay, $DayOf +Year, $IsDST) = localtime(time+$Offset); $Year = $Year + 1900; $Month = $Month + 1; $Day = sprintf ("%02s", $Day); $Month = sprintf ("%02s", $Month); $Hour = sprintf ("%02s", $Hour); $Minute = sprintf ("%02s", $Minute); $Second = sprintf ("%02s", $Second); return $Year."-".$Month."-".$Day." ".$Hour.":".$Minute.":".$Second +; } 1;
      and it's working very well.

        Thanks for sharing your code.

        Does it still work when you add use strict; use warnings; ?

        From my point I see 2 problems in this line {$LogLine = '', $app = shift}; (and similar lines further on). Inserting key/values to a hash is done via the => operator (known as fat comma) or just a plain comma. You use assignment operator =. You escaped a "compilation" error because you use perl-variables as your keys. So the line my $self = {$LogLine = '', $app = shift}; assigns the empty string to $LogLine but does not insert a key named $LogLine into the hash. Perhaps you wanted this?: my $self = {'LogLine' => '', 'app' => shift}; and access it using: $self->{'LogLine'}. The single quotes are superfluous but I am using it just to make a point and to show you that using them will allow you to have a key literally named '$LogLine' but I suspect this is not what you want. So, first use use strict; use warnings; in your package and then I guess you want to replace all $LogLine with 'LogLine' or just LogLine