forked from Drupalcz/drupalcz
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlogview.php
332 lines (313 loc) · 9.06 KB
/
logview.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
<?php
# Acquia Hosting logview.php: A generic web interface for viewing
# local web logs.
#
# INSTALL
#
# Copy this script to anywhere under your site docroot folder,
# and you may want to password protect it using htaccess/htpasswd
#
# ARGUMENTS (sent as GET params)
#
# file: The name of the log file to send (not including path)
# op: What part of the file to send (head|tail|cat|range)
# head: just the top N lines (default is 30 lines)
# tail: just the bottom N lines (default is 30 lines)
# cat: all of it
# range: just send lines N to M (must pass a lines N,M arg)
# menu: show HTML formatted list of local log files
# lines: How many lines to send. Can be an integer or a range
# of two integers in the form of N,M (start_line,stop_line)
#
# No op argument: shows a plain text list of local log files.
#
# EXAMPLES
#
# Get a list of all local log files
# curl -H "Host: example.com" 'http://web-1.example.acquia-sites.com/utils/logview.php'
#
# View the last 50 lines in error.log
# curl -H "Host: example.com" 'http://web-1.example.acquia-sites.com/utils/logview.php?op=tail&lines=50&file=error.log'
#
# Download and save compressed log file
# curl -H "Host: example.com" -o access.log-20091214.gz 'http://web-1.example.acquia-sites.com/utils/logview.php?op=cat&file=access.log-20091214.gz'
#
# Download and decompress a compressed log file
# curl -H "Host: example.com" 'http://web-1.example.acquia-sites.com/utils/logview.php?op=cat&file=access.log-20091214.gz' | gunzip -dc
#
# COPYRIGHT
#
# Copyright (c) 2009 Acquia. Permission granted to modify and redistribute
# this file without restrictions.
#
/**
* Read and verify inputs
*/
global $args;
aq_read_input_args();
aq_set_default_args();
aq_verify_args();
global $site;
aq_get_site_paths();
aq_check_file_access();
/**
* Output
*/
switch ($args['op']) {
// head or tail the log
case 'head':
case 'tail':
$lines = $args['lines'] ? "-n {$args['lines']}" : '-n 30'; # default head and tail to 30 lines
aq_passthru("{$args['op']} $lines");
break;
// cat the entire log file
case 'cat':
aq_passthru('cat');
break;
// output a specific range of lines
case 'range':
aq_passthru("sed -un {$args['lines']},{$args['lines_stop']}p");
break;
// show a HTML menu of log files
case 'menu':
aq_show_main_menu();
break;
// show a plain list of local log files
default:
aq_show_logs_list_as_text();
break;
}
/**
* Read web inputs
*/
function aq_read_input_args() {
global $args;
$args = array('op' => '', 'file' => '', 'lines' => '', 'lines_stop' => '');
$matches = array();
// op: the requested operation
if (isset($_GET['op'])) {
preg_match('@(\w{2,10})@', $_GET['op'], $matches);
$args['op'] = isset($matches[1]) ? $matches[1] : '';
}
// file: the log file name
if (isset($_GET['file'])) {
preg_match('@([\w\.\-]{3,40})@', $_GET['file'], $matches);
$args['file'] = isset($matches[1]) ? $matches[1] : '';
}
// lines: numbers of lines or a range of lines (start_at,stop_at)
if (isset($_GET['lines'])) {
preg_match('@(\d{1,20})(?:,(\d{0,20}))?@', $_GET['lines'], $matches);
$args['lines'] = isset($matches[1]) ? $matches[1] : '';
$args['lines_stop'] = isset($matches[2]) ? $matches[2] : '';
}
}
/**
* Set default args
*/
function aq_set_default_args() {
global $args;
// op: the requested operation
if (!in_array($args['op'], array('head', 'tail', 'cat', 'range', 'menu'))) {
$args['op'] = ''; # default to main menu
}
// file: the log file name
if (!$args['file']) {
$args['file'] = 'access.log';
}
// the file name extension
$args['file_ext'] = array_pop(explode(".", $args['file']));
}
/**
* Verify the arguments
*/
function aq_verify_args() {
global $args;
switch ($args['op']) {
case 'head':
case 'tail':
if ($args['file_ext'] == 'gz') {
aq_error("head and tail command is only available for uncompressed log files");
}
if ($args['lines_stop']) {
aq_error("head and tail do not accept a range option. Use the range command instead.");
}
break;
case 'cat':
if ($args['lines']) {
aq_error("cat does not accept a number of lines option. Use the head, tail, or range command instead.");
}
break;
// output a range of lines
case 'range':
if ($args['file_ext'] == 'gz') {
aq_error("The range command is only available for uncompressed log files");
}
if (!($args['lines'] && $args['lines_stop'])) {
aq_error("range command requires a starting line number and a stop line number: start,stop");
}
if ($args['lines'] > $args['lines_stop']) {
aq_error("The starting line number for the range has to be less than or equal to stop line number.");
}
break;
}
}
/**
* Determine the local log paths of this site
*/
function aq_get_site_paths() {
global $site;
$site = array();
$site['docroot'] = $_SERVER["DOCUMENT_ROOT"];
$matches = array();
preg_match('@^/var/www/html/([\w\.]{2,40})@', $site['docroot'], $matches);
$site['name'] = isset($matches[1]) ? $matches[1] : '';
if (!($site['name'] && $site['docroot'])) {
aq_error("Could not determine the name of this site.");
}
$hostname = `hostname`;
preg_match('@^([\w\-]{2,30})@', $hostname, $matches);
$site['host'] = isset($matches[1]) ? $matches[1] : '';
$site['logdir'] = "/var/log/sites/{$site['name']}/logs/{$site['host']}";
if (!is_dir($site['logdir'])) {
header('HTTP/1.1 404 Not Found');
aq_error("Could not find a local web logs directory for this site.");
}
}
/**
* Check read access for the log file to be read
*/
function aq_check_file_access() {
global $args;
global $site;
// no log file or command specified
if (!($args['op'] && $args['file'])) {
return FALSE;
}
// check that file exists
if (!is_file("{$site['logdir']}/{$args['file']}")) {
header('HTTP/1.1 404 Not Found');
aq_error("<h1>Not Found</h1>The file {$site['logdir']}/{$args['file']} was not found on this server.");
return FALSE;
}
// check file read access
if ($fh = @fopen("{$site['logdir']}/{$args['file']}", 'r')) {
fclose($fh);
return TRUE;
}
// Can't read the file
header('HTTP/1.1 403 Forbidden');
aq_error("<h1>Forbidden</h1>The file {$site['logdir']}/{$args['file']} is not readable to this process.");
}
/**
* Show error and exit
*/
function aq_error($error="logview could not process this request.") {
print "LOGVIEW ERROR: $error";
exit();
}
/**
* Pipe the raw file IO directly back to client
*/
function aq_passthru($op) {
global $args;
global $site;
if ($args['file_ext'] == 'gz') {
header("Content-Type: application/x-gzip");
}
else {
header("Content-Type: text/plain");
}
passthru("$op {$site['logdir']}/{$args['file']}");
}
/**
* Show a fancy HTML main and files list
*/
function aq_show_main_menu() {
global $site;
print "<html>
<head>
<style>
* {
font-family: sans-serif;
font-size: 10pt;
line-height: 1.3;
}
td {
padding-left: 1em;
white-space: nowrap;
}
</style>
</head>
<body>
<div style='width:50%;margin-left:25%;'>
<strong>Acquia Hosting: web server logs for {$site['name']} on {$site['host']}</strong><br/>
{$site['logdir']}";
aq_show_logs_list_as_html();
print "
</div>
</body>
</html>
";
}
/**
* Render an HTML formatted list of log files
*/
function aq_show_logs_list_as_html() {
global $site;
$file_list = '';
aq_get_log_files_list();
foreach ($site['log_files'] as $file => $file_props) {
$file_list .= '<tr>';
// Offer range downloads for uncompressed log files
if ($file_props['ext'] != 'gz' && $file_props['size']) {
$file_list .= "<td><a href=\"?op=head&file=$file\">head</a></td>
<td><a href=\"?op=tail&file=$file\">tail</a></td>
<td><a href=\"?op=range&lines=10,20&file=$file\">lines 10-20</a></td>";
}
else {
$file_list .= "<td colspan='3'></td>";
}
// file size
$file_list .= "<td style='text-align:right;'>{$file_props['size']}</td>";
// file name
$file_list .= "<td><a href=\"?op=cat&file=$file\">$file</a></td>";
$file_list .= '</tr>';
}
print "<table> $file_list </table>";
}
/**
* Render a plain text list of log files
*/
function aq_show_logs_list_as_text() {
global $site;
$file_list = '';
aq_get_log_files_list();
header("Content-Type: text/plain");
foreach ($site['log_files'] as $file => $file_props) {
print "$file\n";
}
}
/**
* Get list of local log files
*/
function aq_get_log_files_list() {
global $site;
$site['log_files'] = array();
if (! ($dh = opendir($site['logdir']))) {
aq_error("Could not read web logs directory for this site.");
}
while (($file = readdir($dh)) !== false) {
// list only regular files:
if (!is_file("{$site['logdir']}/$file")) {
continue;
}
$site['log_files'][$file] = array();
$site['log_files'][$file]['ext'] = array_pop(explode(".", $file));
$site['log_files'][$file]['size'] = number_format(filesize("{$site['logdir']}/$file"));
}
closedir($dh);
if (empty($site['log_files'])) {
aq_error("No files found in {$site['logdir']}.");
}
}
?>