1#**************************************************************
2#
3#  Licensed to the Apache Software Foundation (ASF) under one
4#  or more contributor license agreements.  See the NOTICE file
5#  distributed with this work for additional information
6#  regarding copyright ownership.  The ASF licenses this file
7#  to you under the Apache License, Version 2.0 (the
8#  "License"); you may not use this file except in compliance
9#  with the License.  You may obtain a copy of the License at
10#
11#    http://www.apache.org/licenses/LICENSE-2.0
12#
13#  Unless required by applicable law or agreed to in writing,
14#  software distributed under the License is distributed on an
15#  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16#  KIND, either express or implied.  See the License for the
17#  specific language governing permissions and limitations
18#  under the License.
19#
20#**************************************************************
21
22
23
24package installer::logger;
25
26use installer::files;
27use installer::globals;
28use Time::HiRes qw(gettimeofday tv_interval);
29use English;
30use IO::Handle;
31use strict;
32
33my $StartTime = undef;
34
35sub PrintStackTrace ();
36sub Die ($);
37
38=head1 NAME
39
40    installer::logger
41
42    Logging for the installer modules.
43
44=cut
45
46=head1 DESCRIPTION
47
48    This module is in a transition state from a set of loosly connected functions to a single class.
49
50    There are three globaly available logger objects:
51
52=over
53
54=item $Lang
55
56    is language specific and writes messages to a log file.
57
58=cut
59
60=item $Glob
61
62    is independent of the current language.  Its messages are prepended to each $Lang logger.
63
64=cut
65
66=item $Info
67
68    is for output to the console.
69
70=cut
71
72=back
73
74=cut
75
76
77our $Global = installer::logger->new("glob",
78    'is_save_lines' => 1,
79    'is_print_to_console' => 0,
80    'is_show_relative_time' => 1);
81our $Lang = installer::logger->new("lang",
82    'is_print_to_console' => 0,
83    'is_show_relative_time' => 1,
84    'is_show_log_id' => 1
85    );
86our $Info = installer::logger->new("info",
87    'is_show_relative_time' => 0,
88    'is_show_process_id' => 0,
89    'is_show_log_id' => 0
90    );
91
92
93
94=head2 SetupSimpleLogging ($filename)
95
96    Setup logging so that $Global, $Lang and $Info all print to the console.
97    If $filename is given then logging also goes to that file.
98
99=cut
100sub SetupSimpleLogging (;$)
101{
102    my ($log_filename) = @_;
103
104    $Info = installer::logger->new("info",
105        'is_print_to_console' => 1,
106        'is_show_relative_time' => 1,
107        );
108    $Global = installer::logger->new("glob",
109        'is_print_to_console' => 0,
110        'is_show_relative_time' => 1,
111        'forward' => [$Info]
112        );
113    $Lang = installer::logger->new("lang",
114        'is_print_to_console' => 0,
115        'is_show_relative_time' => 1,
116        'forward' => [$Info]
117        );
118    if (defined $log_filename)
119    {
120        $Info->set_filename($log_filename);
121    }
122    $Info->{'is_print_to_console'} = 1;
123    $installer::globals::quiet = 0;
124    starttime();
125}
126
127
128
129
130=head2 new($class, $id, @arguments)
131
132    Create a new instance of the logger class.
133    @arguments lets you override default values.
134
135=cut
136
137sub new ($$@)
138{
139    my ($class, $id, @arguments) = @_;
140
141    my $self = {
142        'id' => $id,
143        'filename' => "",
144        # When set then lines are printed to this file.
145        'file' => undef,
146        # When true then lines are printed to the console.
147        'is_print_to_console' => 1,
148        'is_save_lines' => 0,
149        # A container of printed lines.  Lines are added only when 'is_save_lines' is true.
150        'lines' => [],
151        # Another logger to which all prints are forwarded.
152        'forward' => [],
153        # A filter function that for example can recoginze build errors.
154        'filter' => undef,
155        # Show relative time
156        'is_show_relative_time' => 0,
157        # Show log id (mostly for debugging the logger)
158        'is_show_log_id' => 0,
159        # Show the process id, useful on the console when doing a multiprocessor build.
160        'is_show_process_id' => 0,
161        # Current indentation
162        'indentation' => "",
163    };
164    while (scalar @arguments >= 2)
165    {
166        my $key = shift @arguments;
167        my $value = shift @arguments;
168        $self->{$key} = $value;
169    }
170
171    bless($self, $class);
172
173    return $self;
174}
175
176
177
178=head2 printf($self, $message, @arguments)
179
180    Identical in syntax and semantics to the usual perl (s)printf.
181
182=cut
183sub printf ($$@)
184{
185    my ($self, $format, @arguments) = @_;
186
187    if ($format =~ /\%\{/)
188    {
189        printf(">%s<\n", $format);
190        PrintStackTrace();
191    }
192    my $message = sprintf($format, @arguments);
193    $self->print($message, 0);
194}
195
196
197
198
199=head2 print ($self, $message, [optional] $force)
200
201    Print the given message.
202    If the optional $force parameter is given and it evaluates to true then the message
203    is printed even when the golbal $installer::globals::quiet is true.
204
205=cut
206sub print ($$;$)
207{
208    my ($self, $message, $force) = @_;
209
210    Die "newline at start of line" if ($message =~ /^\n.+/);
211
212    $force = 0 unless defined $force;
213
214    my $relative_time = tv_interval($StartTime, [gettimeofday()]);
215    foreach my $target ($self, @{$self->{'forward'}})
216    {
217        $target->process_line(
218            $relative_time,
219            $self->{'id'},
220            $PID,
221            $message,
222            $force);
223    }
224}
225
226
227
228
229=head2 process_line ($self, $relative_time, $log_id, $pid, $message, $force)
230
231    Internal function that decides whether to
232    a) write to a log file
233    b) print to the console
234    c) store in an array for later use
235    the preformatted message.
236
237=cut
238sub process_line ($$$$$$)
239{
240    my ($self, $relative_time, $log_id, $pid, $message, $force) = @_;
241
242    # Apply the line filter.
243    if (defined $self->{'filter'})
244    {
245        $message = &{$self->{'filter'}}($relative_time, $log_id, $pid, $message);
246    }
247
248    # Format the line.
249    my $line = "";
250    if ($self->{'is_show_relative_time'})
251    {
252        $line .= sprintf("%12.6f : ", $relative_time);
253    }
254    if ($self->{'is_show_log_id'})
255    {
256        $line .= $log_id . " : ";
257    }
258    if ($self->{'is_show_process_id'})
259    {
260        $line .= $pid . " : ";
261    }
262    $line .= $self->{'indentation'};
263    $line .= $message;
264
265    # Print the line to a file or to the console or store it for later use.
266    my $fid = $self->{'file'};
267    if (defined $fid)
268    {
269        print $fid ($line);
270    }
271    if (($force || ! $installer::globals::quiet)
272        && $self->{'is_print_to_console'})
273    {
274        print($line);
275    }
276    if ($self->{'is_save_lines'})
277    {
278        push @{$self->{'lines'}}, [$relative_time, $log_id, $pid, $message, $force];
279    }
280}
281
282
283
284
285=head2 set_filename (Self, $filename)
286
287    When the name of a writable file is given then all future messages will go to that file.
288    Output to the console is turned off.
289    This method is typically used to tie the language dependent $Lang logger to different log files.
290
291=cut
292sub set_filename ($$)
293{
294    my ($self, $filename) = @_;
295
296    $filename = "" unless defined $filename;
297    if ($self->{'filename'} ne $filename)
298    {
299        if (defined $self->{'file'})
300        {
301            $self->{'is_print_to_console'} = 1;
302            close $self->{'file'};
303            $self->{'file'} = undef;
304        }
305
306        $self->{'filename'} = $filename;
307
308        if ($filename ne "")
309        {
310            open $self->{'file'}, ">", $self->{'filename'}
311            || Die "can not open log file ".$self->{'filename'}." for writing";
312            $self->{'is_print_to_console'} = 0;
313
314            # Make all writes synchronous so that we don't loose any messages on an
315            # 'abrupt' end.
316            my $handle = select $self->{'file'};
317            $| = 1;
318            select $handle;
319        }
320    }
321}
322
323
324
325
326=head2 set_filter ($self, $filter)
327
328    Sets $filter (a function reference) as line filter.  It is applied to each line.
329    The filter can extract information from the given message and modify it before it is printed.
330
331=cut
332sub set_filter ($$)
333{
334    my ($self, $filter) = @_;
335    $self->{'filter'} = $filter;
336}
337
338
339
340
341=head2 add_timestamp ($self, $message)
342
343    Print the given message together with the current (absolute) time.
344
345=cut
346sub add_timestamp ($$)
347{
348	my ($self, $message) = @_;
349
350	my $timestring = get_time_string();
351    $self->printf("%s\t%s", $message, $timestring);
352}
353
354
355
356=head2 copy_lines_from ($self, $other)
357
358    Copy saved lines from another logger object.
359
360=cut
361sub copy_lines_from ($$)
362{
363    my ($self, $other) = @_;
364
365    my $is_print_to_console = $self->{'is_print_to_console'};
366    my $is_save_lines = $self->{'is_save_lines'};
367    my $fid = $self->{'file'};
368
369    foreach my $line (@{$other->{'lines'}})
370    {
371        $self->process_line(@$line);
372    }
373}
374
375
376
377
378=head2 set_forward ($self, $other)
379
380    Set a forwarding target.  All future messages are forwarded (copied) to $other.
381    A typical use is to tie $Info to $Lang so that all messages sent to $Info are
382    printed to the console AND written to the log file.
383
384=cut
385sub set_forward ($$)
386{
387    my ($self, $other) = @_;
388
389    # At the moment at most one forward target is allowed.
390    if (defined $other)
391    {
392        $self->{'forward'} = [$other];
393    }
394    else
395    {
396        $self->{'forward'} = [];
397    }
398}
399
400
401
402
403sub increase_indentation ($)
404{
405    my ($self) = @_;
406    $self->{'indentation'} .= "    ";
407}
408
409
410
411
412sub decrease_indentation ($)
413{
414    my ($self) = @_;
415    $self->{'indentation'} = substr($self->{'indentation'}, 4);
416}
417
418
419
420
421####################################################
422# Including header files into the logfile
423####################################################
424
425sub include_header_into_logfile
426{
427	my ($message) = @_;
428
429	$Lang->print("\n");
430	$Lang->print(get_time_string());
431	$Lang->print("######################################################\n");
432	$Lang->print($message."\n");
433	$Lang->print("######################################################\n");
434}
435
436####################################################
437# Including header files into the logfile
438####################################################
439
440sub include_header_into_globallogfile
441{
442	my ($message) = @_;
443
444	$Global->print("\n");
445	$Global->print(get_time_string());
446	$Global->print("######################################################\n");
447	$Global->print($message."\n");
448	$Global->print("######################################################\n");
449}
450
451####################################################
452# Write timestamp into log file
453####################################################
454
455sub include_timestamp_into_logfile
456{
457    Die "deprected";
458	my ($message) = @_;
459
460	my $infoline;
461	my $timestring = get_time_string();
462    $Lang->printf("%s\t%s", $message, $timestring);
463}
464
465####################################################
466# Writing all variables content into the log file
467####################################################
468
469sub log_hashref
470{
471	my ($hashref) = @_;
472
473    $Global->print("\n");
474    $Global->print("Logging variable settings:\n");
475
476	my $itemkey;
477
478	foreach $itemkey ( keys %{$hashref} )
479	{
480		my $line = "";
481		my $itemvalue = "";
482		if ( $hashref->{$itemkey} ) { $itemvalue = $hashref->{$itemkey}; }
483        $Global->printf("%s=%s\n", $itemkey, $itemvalue);
484	}
485
486    $Global->print("\n");
487}
488
489#########################################################
490# Including global logging info into global log array
491#########################################################
492
493sub globallog
494{
495	my ($message) = @_;
496
497	my $infoline;
498
499    $Global->print("\n");
500    $Global->print(get_time_string());
501    $Global->print("################################################################\n");
502    $Global->print($message."\n");
503    $Global->print("################################################################\n");
504}
505
506###############################################################
507# For each product (new language) a new log file is created.
508# Therefore the global logging has to be saved in this file.
509###############################################################
510
511sub copy_globalinfo_into_logfile
512{
513	for ( my $i = 0; $i <= $#installer::globals::globallogfileinfo; $i++ )
514	{
515		push(@installer::globals::logfileinfo, $installer::globals::globallogfileinfo[$i]);
516	}
517}
518
519###############################################################
520# For each product (new language) a new log file is created.
521# Therefore the global logging has to be saved in this file.
522###############################################################
523
524sub debuginfo
525{
526	my  ( $message ) = @_;
527
528	$message = $message . "\n";
529	push(@installer::globals::functioncalls, $message);
530}
531
532###############################################################
533# Saving the debug information.
534###############################################################
535
536sub savedebug
537{
538	my ( $outputdir ) = @_;
539
540	installer::files::save_file($outputdir . $installer::globals::debugfilename, \@installer::globals::functioncalls);
541	print_message( "... writing debug file " . $outputdir . $installer::globals::debugfilename . "\n" );
542}
543
544###############################################################
545# Starting the time
546###############################################################
547
548sub starttime
549{
550	$installer::globals::starttime = time();
551	$StartTime = [gettimeofday()];
552
553    my $localtime = localtime();
554}
555
556###############################################################
557# Convert time string
558###############################################################
559
560sub convert_timestring
561{
562	my ($secondstring) = @_;
563
564	my $timestring = "";
565
566	if ( $secondstring < 60 )	 # less than a minute
567	{
568		if ( $secondstring < 10 ) { $secondstring = "0" . $secondstring; }
569		$timestring = "00\:$secondstring min\.";
570	}
571	elsif ( $secondstring < 3600 )
572	{
573		my $minutes = $secondstring / 60;
574		my $seconds = $secondstring % 60;
575		if ( $minutes =~ /(\d*)\.\d*/ ) { $minutes = $1; }
576		if ( $minutes < 10 ) { $minutes = "0" . $minutes; }
577		if ( $seconds < 10 ) { $seconds = "0" . $seconds; }
578		$timestring = "$minutes\:$seconds min\.";
579	}
580	else	# more than one hour
581	{
582		my $hours = $secondstring / 3600;
583		my $secondstring = $secondstring % 3600;
584		my $minutes = $secondstring / 60;
585		my $seconds = $secondstring % 60;
586		if ( $hours =~ /(\d*)\.\d*/ ) { $hours = $1; }
587		if ( $minutes =~ /(\d*)\.\d*/ ) { $minutes = $1; }
588		if ( $hours < 10 ) { $hours = "0" . $hours; }
589		if ( $minutes < 10 ) { $minutes = "0" . $minutes; }
590		if ( $seconds < 10 ) { $seconds = "0" . $seconds; }
591		$timestring = "$hours\:$minutes\:$seconds hours";
592	}
593
594	return $timestring;
595}
596
597###############################################################
598# Returning time string for logging
599###############################################################
600
601sub get_time_string
602{
603	my $currenttime = time();
604	$currenttime = $currenttime - $installer::globals::starttime;
605	$currenttime = convert_timestring($currenttime);
606	$currenttime = localtime() . " \(" . $currenttime . "\)\n";
607	return $currenttime;
608}
609
610###############################################################
611# Returning the age of a file (in seconds)
612###############################################################
613
614sub get_file_age
615{
616	my ( $filename ) = @_;
617
618	my $filetime = (stat($filename))[9];
619	my $timediff = time() - $filetime;
620	return $timediff;
621}
622
623###############################################################
624# Stopping the time
625###############################################################
626
627sub stoptime
628{
629    my $localtime = localtime();
630    $Info->printf("stopping log at %s\n", $localtime);
631}
632
633###############################################################
634# Set date string, format: yymmdd
635###############################################################
636
637sub set_installation_date
638{
639	my $datestring = "";
640
641	my @timearray = localtime(time);
642
643	my $day = $timearray[3];
644	my $month = $timearray[4] + 1;
645	my $year = $timearray[5] - 100;
646
647	if ( $year < 10 ) { $year = "0" . $year; }
648	if ( $month < 10 ) { $month = "0" . $month; }
649	if ( $day < 10 ) { $day = "0" . $day; }
650
651	$datestring = $year . $month . $day;
652
653	return $datestring;
654}
655
656###############################################################
657# Console output: messages
658###############################################################
659
660sub print_message
661{
662    Die "print_message is deprecated";
663
664    my $message = shift;
665    chomp $message;
666    my $force = shift || 0;
667    print "$message\n" if ( $force || ! $installer::globals::quiet );
668    return;
669}
670
671sub print_message_without_newline
672{
673    my $message = shift;
674    chomp $message;
675    print "$message" if ( ! $installer::globals::quiet );
676    return;
677}
678
679###############################################################
680# Console output: warnings
681###############################################################
682
683sub print_warning
684{
685    my $message = shift;
686    chomp $message;
687    print STDERR "WARNING: $message";
688    return;
689}
690
691###############################################################
692# Console output: errors
693###############################################################
694
695sub print_error
696{
697    my $message = shift;
698    chomp $message;
699
700    PrintError($message);
701
702    print STDERR "\n";
703    print STDERR "**************************************************\n";
704    print STDERR "ERROR: $message";
705    print STDERR "\n";
706    print STDERR "**************************************************\n";
707    return;
708}
709
710
711
712
713sub PrintError ($@)
714{
715    my ($format, @arguments) = @_;
716
717    $Info->printf("Error: ".$format, @arguments);
718}
719
720
721
722
723=head2 PrintStackTrace()
724    This is for debugging the print and printf methods of the logger class and their use.
725    Therefore we use the Perl print/printf directly and not the logger methods to avoid loops in case of errors.
726=cut
727sub PrintStackTrace ()
728{
729    print "Stack Trace:\n";
730    my $i = 1;
731    while ((my @call_details = (caller($i++))))
732    {
733        printf("%s:%s in function %s\n", $call_details[1], $call_details[2], $call_details[3]);
734    }
735}
736
737
738sub Die ($)
739{
740    my ($message) = @_;
741    PrintStackTrace();
742    die $message;
743}
744
745
746
7471;
748