[Bio] / FigWebServices / Emergency.cgi Repository:
ViewVC logotype

Annotation of /FigWebServices/Emergency.cgi

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.23 - (view) (download)

1 : parrello 1.1 #!/usr/bin/perl -w
2 :    
3 : parrello 1.14 use strict;
4 : parrello 1.16 use Tracer qw(:DEFAULT PrintLine);
5 : parrello 1.14 use CGI;
6 :     use CGI::Cookie;
7 :     use FIG_Config;
8 : parrello 1.15 use FIGRules;
9 : parrello 1.16 use LogReader;
10 : parrello 1.14
11 :     =head1 EmergencyTracing
12 :    
13 :     This script configures emergency tracing and displays the trace file. Emergency tracing
14 :     is used to debug web and command-line applications. A cookie is stored on the user's
15 :     computer that the web applications use to determine what to trace and where to put the
16 :     messages. This script is generally run from the Debug Console in the NMPDR Wiki, where
17 :     you can find further documentation and examples.
18 : parrello 1.1
19 : parrello 1.14 The following CGI parameters are used.
20 : parrello 1.1
21 :     =over 4
22 :    
23 : parrello 1.14 =item action
24 : parrello 1.1
25 : parrello 1.14 Command to execute. C<Activate> will activate tracing and clear the temporary file (if any).
26 : parrello 1.22 C<Terminate> will turn tracing off, C<Show> will display the trace messages or the error
27 :     log, and C<Make> will do a build.
28 : parrello 1.1
29 : parrello 1.14 =item key
30 : parrello 1.1
31 : parrello 1.14 The tracing key (C<key>). This is usually the user's login name (and defaults to that
32 :     value).
33 : parrello 1.1
34 : parrello 1.14 =item level
35 : parrello 1.1
36 : parrello 1.14 The trace level. The higher the trace level, the more messages will appear. This parameter
37 :     is used for the C<Activate> action only.
38 : parrello 1.1
39 : parrello 1.14 =item packages[]
40 : parrello 1.1
41 : parrello 1.14 An array of tracing modules to turn on. Most tracing is configured using the lowest-level
42 :     name of the package containing the trace message, but there are some special names defined as well.
43 :     This parameter is used for the C<Activate> action only.
44 : parrello 1.1
45 : parrello 1.14 =item destination
46 : parrello 1.1
47 : parrello 1.14 Tracing destination. C<FILE> to write to a temporary file, or C<APPEND> to append to a temporary file.
48 :     This parameter is used for the C<Activate> action only.
49 : parrello 1.1
50 : parrello 1.14 =item hours
51 : parrello 1.1
52 : parrello 1.14 The number of hours to leave tracing active (C<hours>). This parameter is used for the C<Activate>
53 :     action only.
54 : parrello 1.1
55 : parrello 1.16 =item section
56 : parrello 1.1
57 : parrello 1.16 The number of bytes to display in the trace file or error log file. This
58 :     parameter is used for the C<Show> action only.
59 :    
60 :     =item direction
61 :    
62 :     C<+> to display the trace or error log file after the starting offset; C<-> to display
63 :     the trace or error log file before the starting offset. The default is C<+>. This parameter
64 :     is used for the C<Show> action only.
65 :    
66 :     =item offset
67 :    
68 :     The starting offset from which the trace file or error log file should be displayed. A positive
69 :     number is treated as an offset from the start of the file. A negative number is treated as an
70 :     offset from the end of the file. A zero is treated as the end of the file if going backward or
71 :     the start of the file if going forward. This parameter is used for the C<Show> action only.
72 :    
73 :     =item logType
74 :    
75 : parrello 1.23 The log to display. C<Trace> will display the trace log, C<Error> will display the error log,
76 :     and C<Command> will display the command-line trace log. This parameter is used for the C<Show>
77 :     action only.
78 : parrello 1.1
79 : parrello 1.15 =item runEnvironment
80 :    
81 :     The environment in which CGI scripts should run. This is stored in the C<SPROUT> cookie
82 :     and is interrogated by the SEED Viewer to determine where it should get the data and
83 :     what display styles it should use.
84 :    
85 : parrello 1.16 =item innerTracing
86 :    
87 :     If specified, then this script will trace at level 3 to a file named C<EmergencyDiagnostics.log>
88 :     in the FIG temporary directory.
89 :    
90 :     =item robotic
91 :    
92 :     TRUE if this user should be considered a robot. This causes the C<test_bot> flag to be set
93 :     for WebApplications.
94 :    
95 : parrello 1.1 =back
96 :    
97 : parrello 1.16 =head2 Globals
98 :    
99 :     =head3 LOG_FINDER
100 :    
101 :     This is a list of directories in which to look for the error log. The first location
102 :     is the B<FIG_Config> variable C<error_log>. Subsequent locations are based on the various
103 :     configurations found on the Argonne MCS machines.
104 :    
105 :     =cut
106 :    
107 :     my @LOG_FINDER = ($FIG_Config::error_log, qw(/var/log/httpd/error_log /var/log/apache2/error.log));
108 :    
109 :     =head3 Methods
110 :    
111 : parrello 1.1 =cut
112 :    
113 : parrello 1.14 # Denote we have not yet started the output page. We need to know this when recovering
114 :     # from errors.
115 :     my $PageStarted = 0;
116 :     # Get the CGI query object.
117 :     my $cgi = CGI->new();
118 :     # Insure we recover from errors.
119 : parrello 1.1 eval {
120 : parrello 1.14 # Get the tracing key.
121 :     my $key = $cgi->param('key') || "";
122 :     if (! $key) {
123 :     die "No tracing key specified.";
124 :     }
125 : parrello 1.22 # Insure we come from a safe place. Safe places include this server, next.nmpdr.org, and
126 : parrello 1.18 # the NMPDR development server. Get the referring URL and parse out the host
127 :     # name. Note that we presume the host is three parts (xxx.yyy.zzz), because
128 : parrello 1.20 # that is true of all our machines, and that there's always a slash at the end of
129 : parrello 1.18 # the name, because we are supposed to be coming from a wiki script.
130 :     my $source = $ENV{HTTP_REFERER};
131 : parrello 1.21 if ($source !~ m#([^/.]+(?:\.[^/.]+){2,3})/#) {
132 : parrello 1.18 die "Invalid referrer $source.";
133 : parrello 1.22 } elsif ($1 ne $FIG_Config::dev_server && $1 ne $ENV{HTTP_HOST} && $1 ne 'next.nmpdr.org') {
134 : parrello 1.18 die "Access denied for $source";
135 : parrello 1.16 } else {
136 : parrello 1.18 # Configure our internal tracing, if necessary.
137 :     if ($cgi->param('innerTracing')) {
138 :     TSetup("3 LogReader Tracer", ">$FIG_Config::temp/EmergencyDiagnostics.log");
139 : parrello 1.14 } else {
140 : parrello 1.18 TSetup("0", "NONE");
141 : parrello 1.14 }
142 : parrello 1.18 # Get the action to take.
143 :     my $action = $cgi->param('action') || 'Show';
144 :     # Get the emergency file name.
145 :     my $efileName = Tracer::EmergencyFileName($key);
146 :     # Process the action.
147 :     if ($action eq 'Activate') {
148 :     # Get the package list. Note that part of our package list may come in as
149 :     # a comma- or space-delimited string, so we split the individual parameters up.
150 :     my @packages = map { split(/\s*[\s,]\s*/, $_) } $cgi->param('packages');
151 :     # Get the other parameters. Note we have defaults for everything.
152 :     my $level = $cgi->param('level') || 0;
153 :     my $destination = $cgi->param('destination') || 'FILE';
154 :     my $hours = $cgi->param('hours') || 4;
155 :     my $environment = $cgi->param('runEnvironment');
156 :     my $robotic = $cgi->param('robotic') || 0;
157 :     # Create cookies so that the tracing key and operating environment can be retrieved
158 :     # by other scripts.
159 :     my @cookies = (CGI::Cookie->new(-name => 'IP', -value => $key, -path => '/'),
160 :     CGI::Cookie->new(-name => 'SPROUT', -value => $environment, -path => '/'),
161 :     CGI::Cookie->new(-name => 'Robot', -value => $robotic, -path => '/'));
162 :     # Make the environment variable more displayable.
163 :     if (! $environment) {
164 :     $environment = (FIGRules::nmpdr_mode($cgi) ? '(Sprout)' : '(FIG)');
165 :     }
166 :     # Indicate the robotic status.
167 :     $environment = ($robotic ? "robotic" : "normal") . " $environment";
168 :     # Start the output page.
169 :     StartPage("Activate Tracing for $key", \@cookies);
170 :     # If there's already a trace file, delete it.
171 :     my $traceFileName = Tracer::EmergencyFileTarget($key);
172 :     if (-f $traceFileName) {
173 :     unlink $traceFileName;
174 :     PrintLine $cgi->p("$traceFileName deleted.");
175 :     }
176 : parrello 1.22 # Does the user want to build? If so, the output goes in here.
177 :     my @buildLines;
178 :     if ($cgi->param('build')) {
179 :     # Rebuild the NMPDR.
180 :     FormatStatus("Rebuilding NMPDR.");
181 :     chdir "$FIG_Config::fig_disk/dist/releases/current";
182 :     @buildLines = `WinBuild/Maker.pl --online`;
183 :     } else {
184 :     FormatStatus("No rebuild requested.");
185 :     }
186 : parrello 1.18 # Turn on emergency tracing.
187 :     Emergency($key, $hours, $destination, $level, @packages);
188 : parrello 1.22 # List the modules activated.
189 :     if (! @packages) {
190 :     FormatStatus("No trace modules activated.");
191 :     } else {
192 :     FormatStatus("Modules activated:");
193 :     print $cgi->ul(map { $cgi->li($_) } @packages);
194 :     }
195 :     # Format the status lines.
196 :     my @dataLines = ("Destination is " . Tracer::EmergencyTracingDest($key, $destination) . ".",
197 :     "Duration is $hours hours (or end of session).",
198 :     "Trace level is $level.",
199 :     "Operating environment is $environment.");
200 :     # Display them all.
201 :     for my $line (@dataLines) {
202 :     FormatStatus($line);
203 :     }
204 :     # Show the build. It's already been formatted as HTML.
205 :     print @buildLines;
206 : parrello 1.18 } elsif ($action eq 'Terminate') {
207 :     # Turn off tracing. We do this by deleting the tracing key file and deleting
208 :     # the environment cookies. First, the cookies.
209 :     my @cookies = (CGI::Cookie->new(-name => 'SPROUT', -value => '', -path => '/', -expires => '-1M'),
210 :     CGI::Cookie->new(-name => 'Robot', -value => '', -path => '/', -expires => '-1M'));
211 :     # We start the page with the cookies included.
212 :     StartPage("Tracing Terminated for $key", \@cookies);
213 :     # Now delete the tracing key file.
214 :     if (-f $efileName) {
215 :     unlink $efileName;
216 :     FormatStatus("Tracing key file deleted.");
217 :     } else {
218 :     FormatError("Tracing was already turned off.");
219 :     }
220 :     } elsif ($action eq 'Show') {
221 :     # Get the number of bytes to display and the offset from which to start.
222 :     my $section = $cgi->param('section');
223 :     my $offset = $cgi->param('offset');
224 :     # Apply the direction to the section size value.
225 :     my $direction = $cgi->param('direction') || '+';
226 :     $section = -$section if $direction eq '-';
227 :     # Handle the end offset.
228 :     $offset = undef if $offset == 0 && $direction eq '-';
229 :     # Get the file type.
230 :     my $type = $cgi->param('logType') || 'Trace';
231 :     # Start the output page. Note that we use a cleaned copy of the file type in the title.
232 : parrello 1.19 my $title = Tracer::Clean("$type Log Display");
233 : parrello 1.18 StartPage($title);
234 :     # Find the log file and compute its column count. The column count is currently
235 :     # the same for both, but this might not always be the case.
236 :     my $fileName;
237 :     my $columnCount = 5;
238 :     if ($type eq 'Trace') {
239 :     # Get the trace file name.
240 :     $fileName = FindTraceLog($key);
241 : parrello 1.23 } elsif ($type eq 'Command') {
242 :     # Compute the command-line key.
243 :     my $key2 = ($key =~ /(.+)\d$/ ? $1 : $key);
244 :     # Get the command-line trace file name.
245 :     $fileName = FindTraceLog($key2);
246 : parrello 1.18 } elsif ($type eq 'Error') {
247 :     # Get the error log name.
248 :     $fileName = FindErrorLog();
249 :     } else {
250 :     FormatError("Unknown log file type \"$type\".");
251 :     }
252 :     # Only proceed if we found a file.
253 :     if ($fileName) {
254 :     # Display the file found.
255 :     ShowLogFile($fileName, $columnCount, $offset, $section);
256 :     }
257 : parrello 1.14 } else {
258 : parrello 1.18 # Here we have an unknown command.
259 :     StartPage("Tracing Console Error", []);
260 :     FormatError("Unknown tracing command $action.");
261 : parrello 1.1 }
262 :     }
263 :     };
264 : parrello 1.14
265 : parrello 1.1 if ($@) {
266 : parrello 1.14 # Here we have a fatal error. Save the message.
267 :     my $errorText = $@;
268 :     # Insure we have a page to which we can write.
269 :     if (! $PageStarted) {
270 :     StartPage("Tracing Console Error", []);
271 :     }
272 :     # Output the error message.
273 :     FormatError("CONSOLE ERROR: $errorText");
274 :     }
275 :     # If we have an output page in progress, close it.
276 :     if ($PageStarted) {
277 : parrello 1.16 PrintLine CGI::end_html();
278 : parrello 1.1 }
279 :    
280 : parrello 1.14 1;
281 : parrello 1.1
282 : parrello 1.14 =head2 Utility Methods
283 : parrello 1.1
284 : parrello 1.16 =head3 ShowLogFile
285 :    
286 :     ShowLogFile($fileName, $columnCount, $offset, $section);
287 :    
288 :     Write a section of a log file to the standard output as an HTML table. The
289 :     column count indicates the format of the file (number of columns of
290 :     data), and the offset and section indicate which section of the file to
291 :     display.
292 :    
293 :     =over 4
294 :    
295 :     =item fileName
296 :    
297 :     Name of the file to display.
298 :    
299 :     =item columnCount
300 :    
301 :     Number of columns of data generally found in the file.
302 :    
303 :     =item offset
304 :    
305 :     Starting location for the display. A positive number or zero is an offset
306 :     from the beginning of the file. A negative number is an offset from the
307 :     end of the file. An undefined value indicates the end of the file.
308 :    
309 :     =item section
310 :    
311 :     Number of bytes of file data to display. A positive number indicates that
312 :     the display extends prior to the offset. A negative number indicates that
313 :     the display extends after the offset.
314 :    
315 :     =back
316 :    
317 :     =cut
318 :    
319 :     sub ShowLogFile {
320 :     # Get the parameters.
321 :     my ($fileName, $columnCount, $offset, $section) = @_;
322 :     # Create a log reader for the file.
323 :     my $logrdr = LogReader->new($fileName, columnCount => $columnCount);
324 :     # Compute the starting offset of the section to display.
325 :     my $fileSize = $logrdr->FileSize();
326 :     my $start;
327 :     if (! defined $offset) {
328 :     $start = $fileSize;
329 :     } elsif ($offset < 0) {
330 :     $start = $fileSize + $offset;
331 :     } else {
332 :     $start = $offset;
333 :     }
334 :     # Insure the start point is valid.
335 :     $start = Tracer::Constrain($start, 0, $fileSize);
336 :     # Apply the section size to compute the end point. If the section size is
337 :     # negative, this could mean moving the start point.
338 :     my $end;
339 :     if ($section < 0) {
340 :     $end = $start;
341 :     $start = Constrain($end + $section, 0, $fileSize);
342 :     } else {
343 :     $end = Constrain($start + $section, 0, $fileSize);
344 :     }
345 :     # Start with a status message.
346 :     my $len = $end - $start;
347 :     FormatStatus("Displaying approximately $len characters starting at position $start.");
348 :     # Position the log reader.
349 :     $logrdr->SetRegion($start, $end);
350 :     # We will be marking the time column in the table as a header whenever it is different from
351 :     # the previous value. We prime the loop with the fragment indicator, so that if we start
352 :     # with a fragment, we don't flag it.
353 :     my $lastTime = LogReader::FragmentString();
354 :     # Similarly, we throw away redundant referrers. We prime the loop with an empty referrer string.
355 :     my $lastReferrer = "";
356 :     # Start the table.
357 :     PrintLine CGI::start_table();
358 :     # Loop through the file, reading records.
359 :     my $record;
360 :     while (defined ($record = $logrdr->GetRecord())) {
361 :     # We'll put the output table row in here.
362 :     my $line = "";
363 :     # Pop off the timestamp.
364 :     my $time = shift @{$record};
365 :     # See if it's changed. Note that these are formatted times, so we do a string compare.
366 :     # They are not numbers!
367 :     if ($time ne $lastTime) {
368 :     # It's a new time stamp. Append it as a row header.
369 :     $line .= CGI::th($time);
370 :     # Clear the remembered referrer so that the user can see it again.
371 :     $lastReferrer = "";
372 :     } else {
373 :     # This is part of the same event, so put in the time stamp normally.
374 :     $line .= CGI::td($time);
375 :     }
376 :     # Save the time stamp for the next time row.
377 :     $lastTime = $time;
378 :     # Pop off the last column. This is the free-form string, and it requires special handling.
379 :     my $string = pop @{$record};
380 :     # Append the middle columns. We take advantage of the distributive capability of the CGI
381 :     # functions: when passed an array reference, they generate one tag pair per array element.
382 :     $line .= CGI::td($record);
383 :     # HTML-escape the final string.
384 :     my $escaped = CGI::escapeHTML($string);
385 :     # Delete leading whitespace.
386 :     $escaped =~ s/^\s+//;
387 :     # Delete the leading tab thingy (if any).
388 :     $escaped =~ s/^\\t//;
389 :     # Check for a referrer indication.
390 :     if ($escaped =~ /(.+),\s+referr?er:\s+(.+)/) {
391 :     # We've got one. Split it from the main message.
392 :     $escaped = $1;
393 :     my $referrer = $2;
394 :     # If it's new, tack it back on with a new-line so it separates from
395 :     # the main message when displayed.
396 :     if ($referrer ne $lastReferrer) {
397 :     $escaped .= "\n Via $referrer";
398 :     # Save it for the next check.
399 :     $lastReferrer = $referrer;
400 :     }
401 :     } else {
402 :     # No referrer, so clear the remembered indicator.
403 :     $lastReferrer = "";
404 :     }
405 :     # The final string may contain multiple lines. The first line is treated as normal text,
406 :     # but subsequent lines are preformatted.
407 :     my ($cell, $others) = split /\s*\n/, $escaped, 2;
408 :     if ($others) {
409 :     # Here there are other lines, so we preformat them. Note that we first strip off any final
410 :     # new-line.
411 :     chomp $others;
412 :     $cell .= CGI::pre($others);
413 :     }
414 :     # Output the string cell.
415 :     $line .= CGI::td($cell);
416 :     # Output the row.
417 :     PrintLine CGI::Tr($line);
418 :     }
419 :     # Close the table.
420 :     PrintLine CGI::end_table();
421 :     }
422 :    
423 : parrello 1.14 =head3 FormatError
424 : parrello 1.1
425 : parrello 1.14 FormatError($message);
426 : parrello 1.1
427 : parrello 1.14 Format an output message as an error. Error messages are shown as block
428 :     quotes, which makes them stand out very violently.
429 : parrello 1.1
430 : parrello 1.14 =over 4
431 : parrello 1.1
432 : parrello 1.14 =item message
433 : parrello 1.1
434 : parrello 1.14 Error message to output.
435 : parrello 1.1
436 :     =back
437 :    
438 :     =cut
439 :    
440 : parrello 1.14 sub FormatError {
441 : parrello 1.1 # Get the parameters.
442 : parrello 1.14 my ($message) = @_;
443 : parrello 1.23 # HTML-escape the error message.
444 :     my $escaped = CGI::escapeHTML($message);
445 :     # Write it out.
446 :     PrintLine CGI::blockquote($escaped);
447 : parrello 1.1 }
448 :    
449 : parrello 1.14 =head3 FormatStatus
450 : parrello 1.4
451 : parrello 1.14 FormatStatus($message);
452 : parrello 1.4
453 : parrello 1.14 Format an output message as a status line. Status lines are shown as
454 :     ordinary paragraphs.
455 : parrello 1.4
456 :     =over 4
457 :    
458 : parrello 1.14 =item message
459 :    
460 :     Status message to output.
461 :    
462 :     =back
463 :    
464 :     =cut
465 :    
466 :     sub FormatStatus{
467 :     # Get the parameters.
468 :     my ($message) = @_;
469 : parrello 1.23 # HTML-escape the status message.
470 :     my $escaped = CGI::escapeHTML($message);
471 :     # Write it out.
472 :     PrintLine CGI::p($escaped);
473 : parrello 1.14 }
474 :    
475 :     =head3 StartPage
476 :    
477 :     StartPage($title, $cookies);
478 : parrello 1.4
479 : parrello 1.15 Start an Html page.
480 : parrello 1.4
481 : parrello 1.14 =over 4
482 :    
483 :     =item title
484 : parrello 1.4
485 : parrello 1.14 Title for the page.
486 : parrello 1.4
487 : parrello 1.14 =item cookies
488 : parrello 1.4
489 : parrello 1.14 Reference to a list of cookies to set.
490 : parrello 1.4
491 :     =back
492 :    
493 :     =cut
494 :    
495 : parrello 1.14 sub StartPage {
496 : parrello 1.4 # Get the parameters.
497 : parrello 1.14 my ($title, $cookies) = @_;
498 :     # Write the HTTP header.
499 :     print CGI::header(-cookie => $cookies);
500 :     # Write the HTML header.
501 : parrello 1.16 PrintLine CGI::start_html(-title => $title,
502 : parrello 1.14 -style => { src => "$FIG_Config::cgi_url/Html/css/WikiConsole.css" });
503 :     # Echo the title as a body heading.
504 : parrello 1.16 PrintLine CGI::h1($title);
505 : parrello 1.14 # Denote we've started the page.
506 :     $PageStarted = 1;
507 : parrello 1.4 }
508 :    
509 : parrello 1.16 =head3 FindTraceLog
510 :    
511 :     my $fileName = FindTraceLog($key);
512 :    
513 :     Find the trace log for the specified key.
514 :    
515 :     =over 4
516 :    
517 :     =item key
518 :    
519 :     Tracing key that identifies the file to display.
520 :    
521 :     =item RETURN
522 :    
523 :     Returns the trace file name, or an undefined value if the trace file is empty or nonexistent.
524 :    
525 :     =back
526 :    
527 :     =cut
528 :    
529 :     sub FindTraceLog {
530 :     # Get the parameters.
531 :     my ($key) = @_;
532 :     # Declare the return variable.
533 :     my $retVal;
534 :     # Get the trace file name.
535 :     my $traceFileName = Tracer::EmergencyFileTarget($key);
536 :     FormatStatus("Tracing data from $traceFileName for $key.");
537 :     # See if tracing is turned on.
538 :     if (! -f $traceFileName) {
539 :     FormatStatus("No trace file found for $key.");
540 :     } elsif (! -s $traceFileName) {
541 :     FormatStatus("The trace file for $key is empty.");
542 :     } else {
543 :     # Here we have a file to read.
544 :     $retVal = $traceFileName;
545 :     }
546 :     # Return the result.
547 :     return $retVal;
548 :     }
549 :    
550 :     =head3 FindErrorLog
551 : parrello 1.4
552 : parrello 1.16 my $fileName = FindErrorLog();
553 : parrello 1.14
554 : parrello 1.16 Return the name of the error log file.
555 :    
556 :     =cut
557 : parrello 1.14
558 : parrello 1.16 sub FindErrorLog {
559 :     # Declare the return variable.
560 :     my $retVal;
561 :     # Loop through the possible log file names until we find one.
562 :     for my $log (@LOG_FINDER) { last if $retVal;
563 :     # We do a defined check here in case the FIG_Config variable is not set.
564 :     if (defined $log && -f $log) {
565 :     $retVal = $log;
566 :     }
567 :     }
568 :     # Check for unusual conditions.
569 :     if (! defined $retVal) {
570 :     FormatError("Error log file not found. Locate the log and put its name in \$FIG_Config::error_log.");
571 :     } elsif (! -s $retVal) {
572 :     FormatStatus("Error log file \"$retVal\" is empty.");
573 :     # Denote we haven't found an error log.
574 :     undef $retVal;
575 :     }
576 :     # Return the result.
577 :     return $retVal;
578 :     }

MCS Webmaster
ViewVC Help
Powered by ViewVC 1.0.3