Skip to content

Commit 4c469c2

Browse files
committed
Added Logfile parser, output parsed info in stdout, move summary in ouput along with abnormal ua
1 parent 85aac65 commit 4c469c2

File tree

2 files changed

+211
-0
lines changed

2 files changed

+211
-0
lines changed

bin/uaparser

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ $application->add(new ConvertCommand($resourceDirectory, $defaultYamlFile));
2626
$application->add(new UpdateCommand($resourceDirectory));
2727
$application->add(new ParserCommand());
2828
$application->add(new LogfileCommand());
29+
$application->add(new LogfileParseCommand());
2930
$application->add(new FetchCommand($defaultYamlFile));
3031

3132
$application->run();
Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
<?php
2+
/**
3+
* ua-parser
4+
*
5+
* Copyright (c) 2011-2012 Dave Olsen, http://dmolsen.com
6+
*
7+
* Released under the MIT license
8+
*/
9+
namespace UAParser\Command;
10+
11+
use Symfony\Component\Console\Command\Command;
12+
use Symfony\Component\Console\Input\InputArgument;
13+
use Symfony\Component\Console\Input\InputInterface;
14+
use Symfony\Component\Console\Input\InputOption;
15+
use Symfony\Component\Console\Output\OutputInterface;
16+
use Symfony\Component\Filesystem\Filesystem;
17+
use Symfony\Component\Finder\Finder;
18+
use Symfony\Component\Finder\SplFileInfo;
19+
use UAParser\Exception\InvalidArgumentException;
20+
use UAParser\Exception\ReaderException;
21+
use UAParser\Parser;
22+
use UAParser\Result\Client;
23+
use UAParser\Util\Logfile\AbstractReader;
24+
25+
class LogfileParseCommand extends Command
26+
{
27+
protected function configure()
28+
{
29+
$this
30+
->setName('ua-parser:parselog')
31+
->setDescription('Parses the supplied webserver log file.')
32+
->addArgument(
33+
'output',
34+
InputArgument::REQUIRED,
35+
'Path to output log file'
36+
)
37+
->addOption(
38+
'log-file',
39+
'f',
40+
InputOption::VALUE_REQUIRED,
41+
'Path to a webserver log file'
42+
)
43+
->addOption(
44+
'log-dir',
45+
'd',
46+
InputOption::VALUE_REQUIRED,
47+
'Path to webserver log directory'
48+
)
49+
->addOption(
50+
'include',
51+
'i',
52+
InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED,
53+
'Include glob expressions for log files in the log directory',
54+
array('*.log', '*.log*.gz', '*.log*.bz2')
55+
)
56+
->addOption(
57+
'exclude',
58+
'e',
59+
InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED,
60+
'Exclude glob expressions for log files in the log directory',
61+
array('*error*')
62+
)
63+
;
64+
}
65+
66+
protected function execute(InputInterface $input, OutputInterface $output)
67+
{
68+
if (!$input->getOption('log-file') && !$input->getOption('log-dir')) {
69+
throw InvalidArgumentException::oneOfCommandArguments('log-file', 'log-dir');
70+
}
71+
72+
$parser = Parser::create();
73+
$undefinedClients = array();
74+
/** @var $file SplFileInfo */
75+
$summary = '';
76+
foreach ($this->getFiles($input) as $file) {
77+
78+
$path = $this->getPath($file);
79+
$lines = file($path);
80+
81+
if (empty($lines)) {
82+
continue;
83+
}
84+
85+
$firstLine = reset($lines);
86+
87+
$reader = AbstractReader::factory($firstLine);
88+
if (!$reader) {
89+
$output->writeln(sprintf('Could not find reader for file "%s"', $file->getPathname()));
90+
$output->writeln('');
91+
continue;
92+
}
93+
94+
$summary .= sprintf('Analyzing "%s"', $file->getPathname());
95+
$summary .= "\n";
96+
97+
$count = 1;
98+
$totalCount = count($lines);
99+
foreach ($lines as $line) {
100+
101+
try {
102+
$userAgentString = $reader->read($line);
103+
} catch (ReaderException $e) {
104+
if (!$input->getOption('no-prog'))
105+
$count = $this->outputProgress($summary, 'E', $count, $totalCount);
106+
continue;
107+
}
108+
109+
$client = $parser->parse($userAgentString);
110+
111+
$result = $this->getResult($client);
112+
if ($result !== '.') {
113+
$undefinedClients[] = json_encode(
114+
array($client->toString(), $userAgentString),
115+
JSON_UNESCAPED_SLASHES
116+
);
117+
}
118+
119+
$count = $this->outputProgress($summary, $result, $count, $totalCount);
120+
if ($result === '.') {
121+
$client = (array)$client;
122+
$client['seq'] = $count;
123+
$output->writeln(json_encode($client, JSON_PRETTY_PRINT));
124+
}
125+
}
126+
$this->outputProgress($summary, '', $count - 1, $totalCount, true);
127+
$output->writeln('');
128+
}
129+
130+
$undefinedClients = $this->filter($undefinedClients);
131+
132+
$fs = new Filesystem();
133+
$fs->dumpFile($input->getArgument('output'), $summary."\n".join(PHP_EOL, $undefinedClients));
134+
}
135+
136+
private function outputProgress(&$summary, $result, $count, $totalCount, $end = false)
137+
{
138+
if (($count % 70) === 0 || $end) {
139+
$formatString = '%s %' . strlen($totalCount) . 'd / %-' . strlen($totalCount) . 'd (%3d%%)';
140+
$result = $end ? str_repeat(' ', 70 - ($count % 70)) : $result;
141+
$summary .= sprintf($formatString, $result, $count, $totalCount, $count / $totalCount * 100)."\n";
142+
} else {
143+
$summary .= $result;
144+
}
145+
146+
return $count + 1;
147+
}
148+
149+
private function getResult(Client $client)
150+
{
151+
if ($client->device->family === 'Spider') {
152+
return 'C';
153+
} elseif ($client->ua->family === 'Other') {
154+
return 'U';
155+
} elseif ($client->os->family === 'Other') {
156+
return 'O';
157+
} elseif ($client->device->family === 'Generic Smartphone') {
158+
return 'S';
159+
} elseif ($client->device->family === 'Generic Feature Phone') {
160+
return 'F';
161+
}
162+
163+
return '.';
164+
}
165+
166+
private function getFiles(InputInterface $input)
167+
{
168+
$finder = Finder::create();
169+
170+
if ($input->getOption('log-file')) {
171+
$file = $input->getOption('log-file');
172+
$finder->append(Finder::create()->in(dirname($file))->name(basename($file)));
173+
}
174+
175+
if ($input->getOption('log-dir')) {
176+
$dirFinder = Finder::create()
177+
->in($input->getOption('log-dir'));
178+
array_map(array($dirFinder, 'name'), $input->getOption('include'));
179+
array_map(array($dirFinder, 'notName'), $input->getOption('exclude'));
180+
181+
$finder->append($dirFinder);
182+
}
183+
184+
return $finder;
185+
}
186+
187+
private function filter(array $lines)
188+
{
189+
return array_values(array_unique($lines));
190+
}
191+
192+
private function getPath(SplFileInfo $file)
193+
{
194+
switch ($file->getExtension()) {
195+
case 'gz':
196+
$path = 'compress.zlib://' . $file->getPathname();
197+
break;
198+
199+
case 'bz2':
200+
$path = 'compress.bzip2://' . $file->getPathname();
201+
break;
202+
203+
default:
204+
$path = $file->getPathname();
205+
break;
206+
}
207+
208+
return $path;
209+
}
210+
}

0 commit comments

Comments
 (0)