Skip to content

Commit ad4f488

Browse files
committed
Merge remote-tracking branch 'flowpl/master'
2 parents dcfae95 + 04648db commit ad4f488

3 files changed

Lines changed: 358 additions & 0 deletions

File tree

README.mdown

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ Formatters
210210
- _ElasticaFormatter_: Used to format log records into an Elastica\Document object, only useful for the ElasticSearchHandler.
211211
- _LogglyFormatter_: Used to format log records into Loggly messages, only useful for the LogglyHandler.
212212
- _FlowdockFormatter_: Used to format log records into Flowdock messages, only useful for the FlowdockHandler.
213+
- _MongoDBFormatter_: Converts \DateTime instances to \MongoDate and objects recursively to arrays, only useful with the MongoDBHandler.
213214

214215
Processors
215216
----------
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Monolog package.
5+
*
6+
* (c) Jordi Boggiano <j.boggiano@seld.be>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Monolog\Formatter;
13+
14+
/**
15+
* Formats a record for use with the MongoDBHandler.
16+
*
17+
* @author Florian Plattner <me@florianplattner.de>
18+
*/
19+
class MongoDBFormatter implements FormatterInterface
20+
{
21+
private $exceptionTraceAsString;
22+
private $maxNestingLevel;
23+
24+
/**
25+
* @param int $maxNestingLevel 0 means infinite nesting, the $record itself is level 1, $record['context'] is 2
26+
* @param bool $exceptionTraceAsString set to false to log exception traces as a sub documents instead of strings
27+
*/
28+
public function __construct($maxNestingLevel = 3, $exceptionTraceAsString = true)
29+
{
30+
$this->maxNestingLevel = max($maxNestingLevel, 0);
31+
$this->exceptionTraceAsString = (bool) $exceptionTraceAsString;
32+
}
33+
34+
/**
35+
* {@inheritDoc}
36+
*/
37+
public function format (array $record)
38+
{
39+
return $this->formatArray($record);
40+
}
41+
42+
/**
43+
* {@inheritDoc}
44+
*/
45+
public function formatBatch (array $records)
46+
{
47+
foreach ($records as $key => $record) {
48+
$records[$key] = $this->format($record);
49+
}
50+
51+
return $records;
52+
}
53+
54+
protected function formatArray (array $record, $nestingLevel = 0)
55+
{
56+
if ($this->maxNestingLevel == 0 || $nestingLevel <= $this->maxNestingLevel) {
57+
foreach ($record as $name => $value) {
58+
if ($value instanceof \DateTime) {
59+
$record[$name] = $this->formatDate($value, $nestingLevel + 1);
60+
} elseif ($value instanceof \Exception) {
61+
$record[$name] = $this->formatException($value, $nestingLevel + 1);
62+
} elseif (is_array($value)) {
63+
$record[$name] = $this->formatArray($value, $nestingLevel + 1);
64+
} elseif (is_object($value)) {
65+
$record[$name] = $this->formatObject($value, $nestingLevel + 1);
66+
}
67+
}
68+
} else {
69+
$record = '[...]';
70+
}
71+
72+
return $record;
73+
}
74+
75+
protected function formatObject ($value, $nestingLevel)
76+
{
77+
$objectVars = get_object_vars($value);
78+
$objectVars['class'] = get_class($value);
79+
return $this->formatArray($objectVars, $nestingLevel);
80+
}
81+
82+
protected function formatException (\Exception $exception, $nestingLevel)
83+
{
84+
$formattedException = array(
85+
'class' => get_class($exception),
86+
'message' => $exception->getMessage(),
87+
'code' => $exception->getCode(),
88+
'file' => $exception->getFile() . ':' . $exception->getLine(),
89+
);
90+
91+
if ($this->exceptionTraceAsString === true) {
92+
$formattedException['trace'] = $exception->getTraceAsString();
93+
} else {
94+
$formattedException['trace'] = $exception->getTrace();
95+
}
96+
97+
return $this->formatArray($formattedException, $nestingLevel);
98+
}
99+
100+
protected function formatDate (\DateTime $value, $nestingLevel)
101+
{
102+
return new \MongoDate($value->getTimestamp());
103+
}
104+
}
Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
<?php
2+
3+
namespace Monolog\Formatter;
4+
5+
use Monolog\Logger;
6+
7+
/**
8+
* @author Florian Plattner <me@florianplattner.de>
9+
*/
10+
class MongoDBFormatterTest extends \PHPUnit_Framework_TestCase
11+
{
12+
public function setUp()
13+
{
14+
if (!class_exists('MongoDate')) {
15+
$this->markTestSkipped('mongo extension not installed');
16+
}
17+
}
18+
19+
public function constructArgumentProvider()
20+
{
21+
return array(
22+
array(1, true, 1, true),
23+
array(0, false, 0, false),
24+
);
25+
}
26+
27+
/**
28+
* @param $traceDepth
29+
* @param $traceAsString
30+
* @param $expectedTraceDepth
31+
* @param $expectedTraceAsString
32+
*
33+
* @dataProvider constructArgumentProvider
34+
*/
35+
public function testConstruct($traceDepth, $traceAsString, $expectedTraceDepth, $expectedTraceAsString)
36+
{
37+
$formatter = new MongoDBFormatter($traceDepth, $traceAsString);
38+
39+
$reflTrace = new \ReflectionProperty($formatter, 'exceptionTraceAsString');
40+
$reflTrace->setAccessible(true);
41+
$this->assertEquals($expectedTraceAsString, $reflTrace->getValue($formatter));
42+
43+
$reflDepth = new\ReflectionProperty($formatter, 'maxNestingLevel');
44+
$reflDepth->setAccessible(true);
45+
$this->assertEquals($expectedTraceDepth, $reflDepth->getValue($formatter));
46+
}
47+
48+
public function testSimpleFormat()
49+
{
50+
$record = array(
51+
'message' => 'some log message',
52+
'context' => array(),
53+
'level' => Logger::WARNING,
54+
'level_name' => Logger::getLevelName(Logger::WARNING),
55+
'channel' => 'test',
56+
'datetime' => new \DateTime('2014-02-01 00:00:00'),
57+
'extra' => array(),
58+
);
59+
60+
$formatter = new MongoDBFormatter();
61+
$formattedRecord = $formatter->format($record);
62+
63+
$this->assertCount(7, $formattedRecord);
64+
$this->assertEquals('some log message', $formattedRecord['message']);
65+
$this->assertEquals(array(), $formattedRecord['context']);
66+
$this->assertEquals(Logger::WARNING, $formattedRecord['level']);
67+
$this->assertEquals(Logger::getLevelName(Logger::WARNING), $formattedRecord['level_name']);
68+
$this->assertEquals('test', $formattedRecord['channel']);
69+
$this->assertInstanceOf('\MongoDate', $formattedRecord['datetime']);
70+
$this->assertEquals('0.00000000 1391212800', $formattedRecord['datetime']->__toString());
71+
$this->assertEquals(array(), $formattedRecord['extra']);
72+
}
73+
74+
public function testRecursiveFormat()
75+
{
76+
$someObject = new \stdClass();
77+
$someObject->foo = 'something';
78+
$someObject->bar = 'stuff';
79+
80+
$record = array(
81+
'message' => 'some log message',
82+
'context' => array(
83+
'stuff' => new \DateTime('2014-02-01 02:31:33'),
84+
'some_object' => $someObject,
85+
'context_string' => 'some string',
86+
'context_int' => 123456,
87+
'except' => new \Exception('exception message', 987),
88+
),
89+
'level' => Logger::WARNING,
90+
'level_name' => Logger::getLevelName(Logger::WARNING),
91+
'channel' => 'test',
92+
'datetime' => new \DateTime('2014-02-01 00:00:00'),
93+
'extra' => array(),
94+
);
95+
96+
$formatter = new MongoDBFormatter();
97+
$formattedRecord = $formatter->format($record);
98+
99+
$this->assertCount(5, $formattedRecord['context']);
100+
$this->assertInstanceOf('\MongoDate', $formattedRecord['context']['stuff']);
101+
$this->assertEquals('0.00000000 1391221893', $formattedRecord['context']['stuff']->__toString());
102+
$this->assertEquals(
103+
array(
104+
'foo' => 'something',
105+
'bar' => 'stuff',
106+
'class' => 'stdClass',
107+
),
108+
$formattedRecord['context']['some_object']
109+
);
110+
$this->assertEquals('some string', $formattedRecord['context']['context_string']);
111+
$this->assertEquals(123456, $formattedRecord['context']['context_int']);
112+
113+
$this->assertCount(6, $formattedRecord['context']['except']);
114+
$this->assertEquals('exception message', $formattedRecord['context']['except']['message']);
115+
$this->assertEquals(987, $formattedRecord['context']['except']['code']);
116+
$this->assertInternalType('string', $formattedRecord['context']['except']['file']);
117+
$this->assertInternalType('integer', $formattedRecord['context']['except']['code']);
118+
$this->assertInternalType('string', $formattedRecord['context']['except']['trace']);
119+
$this->assertEquals('Exception', $formattedRecord['context']['except']['class']);
120+
}
121+
122+
public function testFormatDepthArray()
123+
{
124+
$record = array(
125+
'message' => 'some log message',
126+
'context' => array(
127+
'nest2' => array(
128+
'property' => 'anything',
129+
'nest3' => array(
130+
'nest4' => 'value',
131+
'property' => 'nothing'
132+
)
133+
)
134+
),
135+
'level' => Logger::WARNING,
136+
'level_name' => Logger::getLevelName(Logger::WARNING),
137+
'channel' => 'test',
138+
'datetime' => new \DateTime('2014-02-01 00:00:00'),
139+
'extra' => array(),
140+
);
141+
142+
$formatter = new MongoDBFormatter(2);
143+
$formattedResult = $formatter->format($record);
144+
145+
$this->assertEquals(
146+
array(
147+
'nest2' => array(
148+
'property' => 'anything',
149+
'nest3' => '[...]',
150+
)
151+
),
152+
$formattedResult['context']
153+
);
154+
}
155+
156+
public function testFormatDepthArrayInfiniteNesting()
157+
{
158+
$record = array(
159+
'message' => 'some log message',
160+
'context' => array(
161+
'nest2' => array(
162+
'property' => 'something',
163+
'nest3' => array(
164+
'property' => 'anything',
165+
'nest4' => array(
166+
'property' => 'nothing',
167+
),
168+
)
169+
)
170+
),
171+
'level' => Logger::WARNING,
172+
'level_name' => Logger::getLevelName(Logger::WARNING),
173+
'channel' => 'test',
174+
'datetime' => new \DateTime('2014-02-01 00:00:00'),
175+
'extra' => array(),
176+
);
177+
178+
$formatter = new MongoDBFormatter(0);
179+
$formattedResult = $formatter->format($record);
180+
181+
$this->assertEquals(
182+
array(
183+
'nest2' => array(
184+
'property' => 'something',
185+
'nest3' => array(
186+
'property' => 'anything',
187+
'nest4' => array(
188+
'property' => 'nothing',
189+
)
190+
),
191+
)
192+
),
193+
$formattedResult['context']
194+
);
195+
}
196+
197+
public function testFormatDepthObjects()
198+
{
199+
$someObject = new \stdClass();
200+
$someObject->property = 'anything';
201+
$someObject->nest3 = new \stdClass();
202+
$someObject->nest3->property = 'nothing';
203+
$someObject->nest3->nest4 = 'invisible';
204+
205+
$record = array(
206+
'message' => 'some log message',
207+
'context' => array(
208+
'nest2' => $someObject
209+
),
210+
'level' => Logger::WARNING,
211+
'level_name' => Logger::getLevelName(Logger::WARNING),
212+
'channel' => 'test',
213+
'datetime' => new \DateTime('2014-02-01 00:00:00'),
214+
'extra' => array(),
215+
);
216+
217+
$formatter = new MongoDBFormatter(2, true);
218+
$formattedResult = $formatter->format($record);
219+
220+
$this->assertEquals(
221+
array(
222+
'nest2' => array(
223+
'property' => 'anything',
224+
'nest3' => '[...]',
225+
'class' => 'stdClass',
226+
),
227+
),
228+
$formattedResult['context']
229+
);
230+
}
231+
232+
public function testFormatDepthException()
233+
{
234+
$record = array(
235+
'message' => 'some log message',
236+
'context' => array(
237+
'nest2' => new \Exception('exception message', 987),
238+
),
239+
'level' => Logger::WARNING,
240+
'level_name' => Logger::getLevelName(Logger::WARNING),
241+
'channel' => 'test',
242+
'datetime' => new \DateTime('2014-02-01 00:00:00'),
243+
'extra' => array(),
244+
);
245+
246+
$formatter = new MongoDBFormatter(2, false);
247+
$formattedRecord = $formatter->format($record);
248+
249+
$this->assertEquals('exception message', $formattedRecord['context']['nest2']['message']);
250+
$this->assertEquals(987, $formattedRecord['context']['nest2']['code']);
251+
$this->assertEquals('[...]', $formattedRecord['context']['nest2']['trace']);
252+
}
253+
}

0 commit comments

Comments
 (0)