1: <?php
2: /**
3: * Licensed to the Apache Software Foundation (ASF) under one or more
4: * contributor license agreements. See the NOTICE file distributed with
5: * this work for additional information regarding copyright ownership.
6: * The ASF licenses this file to You under the Apache License, Version 2.0
7: * (the "License"); you may not use this file except in compliance with
8: * the License. You may obtain a copy of the License at
9: *
10: * http://www.apache.org/licenses/LICENSE-2.0
11: *
12: * Unless required by applicable law or agreed to in writing, software
13: * distributed under the License is distributed on an "AS IS" BASIS,
14: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15: * See the License for the specific language governing permissions and
16: * limitations under the License.
17: */
18:
19: /**
20: * Appender for writing to MongoDB.
21: *
22: * This class was originally contributed by Vladimir Gorej.
23: *
24: * ## Configurable parameters: ##
25: *
26: * - **host** - Server on which mongodb instance is located.
27: * - **port** - Port on which the instance is bound.
28: * - **databaseName** - Name of the database to which to log.
29: * - **collectionName** - Name of the target collection within the given database.
30: * - **username** - Username used to connect to the database.
31: * - **password** - Password used to connect to the database.
32: * - **timeout** - For how long the driver should try to connect to the database (in milliseconds).
33: *
34: * @version $Revision: 1346363 $
35: * @package log4php
36: * @subpackage appenders
37: * @since 2.1
38: * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
39: * @link http://logging.apache.org/log4php/docs/appenders/mongodb.html Appender documentation
40: * @link http://github.com/log4mongo/log4mongo-php Vladimir Gorej's original submission.
41: * @link http://www.mongodb.org/ MongoDB website.
42: */
43: class LoggerAppenderMongoDB extends LoggerAppender {
44:
45: // ******************************************
46: // ** Constants **
47: // ******************************************
48:
49: /** Default prefix for the {@link $host}. */
50: const DEFAULT_MONGO_URL_PREFIX = 'mongodb://';
51:
52: /** Default value for {@link $host}, without a prefix. */
53: const DEFAULT_MONGO_HOST = 'localhost';
54:
55: /** Default value for {@link $port} */
56: const DEFAULT_MONGO_PORT = 27017;
57:
58: /** Default value for {@link $databaseName} */
59: const DEFAULT_DB_NAME = 'log4php_mongodb';
60:
61: /** Default value for {@link $collectionName} */
62: const DEFAULT_COLLECTION_NAME = 'logs';
63:
64: /** Default value for {@link $timeout} */
65: const DEFAULT_TIMEOUT_VALUE = 3000;
66:
67: // ******************************************
68: // ** Configurable parameters **
69: // ******************************************
70:
71: /** Server on which mongodb instance is located. */
72: protected $host;
73:
74: /** Port on which the instance is bound. */
75: protected $port;
76:
77: /** Name of the database to which to log. */
78: protected $databaseName;
79:
80: /** Name of the collection within the given database. */
81: protected $collectionName;
82:
83: /** Username used to connect to the database. */
84: protected $userName;
85:
86: /** Password used to connect to the database. */
87: protected $password;
88:
89: /** Timeout value used when connecting to the database (in milliseconds). */
90: protected $timeout;
91:
92: // ******************************************
93: // ** Member variables **
94: // ******************************************
95:
96: /**
97: * Connection to the MongoDB instance.
98: * @var Mongo
99: */
100: protected $connection;
101:
102: /**
103: * The collection to which log is written.
104: * @var MongoCollection
105: */
106: protected $collection;
107:
108: public function __construct($name = '') {
109: parent::__construct($name);
110: $this->host = self::DEFAULT_MONGO_URL_PREFIX . self::DEFAULT_MONGO_HOST;
111: $this->port = self::DEFAULT_MONGO_PORT;
112: $this->databaseName = self::DEFAULT_DB_NAME;
113: $this->collectionName = self::DEFAULT_COLLECTION_NAME;
114: $this->timeout = self::DEFAULT_TIMEOUT_VALUE;
115: $this->requiresLayout = false;
116: }
117:
118: /**
119: * Setup db connection.
120: * Based on defined options, this method connects to the database and
121: * creates a {@link $collection}.
122: */
123: public function activateOptions() {
124: try {
125: $this->connection = new Mongo(sprintf('%s:%d', $this->host, $this->port), array('timeout' => $this->timeout));
126: $db = $this->connection->selectDB($this->databaseName);
127: if ($this->userName !== null && $this->password !== null) {
128: $authResult = $db->authenticate($this->userName, $this->password);
129: if ($authResult['ok'] == floatval(0)) {
130: throw new Exception($authResult['errmsg'], $authResult['ok']);
131: }
132: }
133: $this->collection = $db->selectCollection($this->collectionName);
134: } catch (MongoConnectionException $ex) {
135: $this->closed = true;
136: $this->warn(sprintf('Failed to connect to mongo deamon: %s', $ex->getMessage()));
137: } catch (InvalidArgumentException $ex) {
138: $this->closed = true;
139: $this->warn(sprintf('Error while selecting mongo database: %s', $ex->getMessage()));
140: } catch (Exception $ex) {
141: $this->closed = true;
142: $this->warn('Invalid credentials for mongo database authentication');
143: }
144: }
145:
146: /**
147: * Appends a new event to the mongo database.
148: *
149: * @param LoggerLoggingEvent $event
150: */
151: public function append(LoggerLoggingEvent $event) {
152: try {
153: if ($this->collection != null) {
154: $this->collection->insert($this->format($event));
155: }
156: } catch (MongoCursorException $ex) {
157: $this->warn(sprintf('Error while writing to mongo collection: %s', $ex->getMessage()));
158: }
159: }
160:
161: /**
162: * Converts the logging event into an array which can be logged to mongodb.
163: *
164: * @param LoggerLoggingEvent $event
165: * @return array The array representation of the logging event.
166: */
167: protected function format(LoggerLoggingEvent $event) {
168: $timestampSec = (int) $event->getTimestamp();
169: $timestampUsec = (int) (($event->getTimestamp() - $timestampSec) * 1000000);
170:
171: $document = array(
172: 'timestamp' => new MongoDate($timestampSec, $timestampUsec),
173: 'level' => $event->getLevel()->toString(),
174: 'thread' => (int) $event->getThreadName(),
175: 'message' => $event->getMessage(),
176: 'loggerName' => $event->getLoggerName()
177: );
178:
179: $locationInfo = $event->getLocationInformation();
180: if ($locationInfo != null) {
181: $document['fileName'] = $locationInfo->getFileName();
182: $document['method'] = $locationInfo->getMethodName();
183: $document['lineNumber'] = ($locationInfo->getLineNumber() == 'NA') ? 'NA' : (int) $locationInfo->getLineNumber();
184: $document['className'] = $locationInfo->getClassName();
185: }
186:
187: $throwableInfo = $event->getThrowableInformation();
188: if ($throwableInfo != null) {
189: $document['exception'] = $this->formatThrowable($throwableInfo->getThrowable());
190: }
191:
192: return $document;
193: }
194:
195: /**
196: * Converts an Exception into an array which can be logged to mongodb.
197: *
198: * Supports innner exceptions (PHP >= 5.3)
199: *
200: * @param Exception $ex
201: * @return array
202: */
203: protected function formatThrowable(Exception $ex) {
204: $array = array(
205: 'message' => $ex->getMessage(),
206: 'code' => $ex->getCode(),
207: 'stackTrace' => $ex->getTraceAsString(),
208: );
209:
210: if (method_exists($ex, 'getPrevious') && $ex->getPrevious() !== null) {
211: $array['innerException'] = $this->formatThrowable($ex->getPrevious());
212: }
213:
214: return $array;
215: }
216:
217: /**
218: * Closes the connection to the logging database
219: */
220: public function close() {
221: if($this->closed != true) {
222: $this->collection = null;
223: if ($this->connection !== null) {
224: $this->connection->close();
225: $this->connection = null;
226: }
227: $this->closed = true;
228: }
229: }
230:
231: /**
232: * Sets the value of {@link $host} parameter.
233: * @param string $host
234: */
235: public function setHost($host) {
236: if (!preg_match('/^mongodb\:\/\//', $host)) {
237: $host = self::DEFAULT_MONGO_URL_PREFIX . $host;
238: }
239: $this->host = $host;
240: }
241:
242: /**
243: * Returns the value of {@link $host} parameter.
244: * @return string
245: */
246: public function getHost() {
247: return $this->host;
248: }
249:
250: /**
251: * Sets the value of {@link $port} parameter.
252: * @param int $port
253: */
254: public function setPort($port) {
255: $this->setPositiveInteger('port', $port);
256: }
257:
258: /**
259: * Returns the value of {@link $port} parameter.
260: * @return int
261: */
262: public function getPort() {
263: return $this->port;
264: }
265:
266: /**
267: * Sets the value of {@link $databaseName} parameter.
268: * @param string $databaseName
269: */
270: public function setDatabaseName($databaseName) {
271: $this->setString('databaseName', $databaseName);
272: }
273:
274: /**
275: * Returns the value of {@link $databaseName} parameter.
276: * @return string
277: */
278: public function getDatabaseName() {
279: return $this->databaseName;
280: }
281:
282: /**
283: * Sets the value of {@link $collectionName} parameter.
284: * @param string $collectionName
285: */
286: public function setCollectionName($collectionName) {
287: $this->setString('collectionName', $collectionName);
288: }
289:
290: /**
291: * Returns the value of {@link $collectionName} parameter.
292: * @return string
293: */
294: public function getCollectionName() {
295: return $this->collectionName;
296: }
297:
298: /**
299: * Sets the value of {@link $userName} parameter.
300: * @param string $userName
301: */
302: public function setUserName($userName) {
303: $this->setString('userName', $userName, true);
304: }
305:
306: /**
307: * Returns the value of {@link $userName} parameter.
308: * @return string
309: */
310: public function getUserName() {
311: return $this->userName;
312: }
313:
314: /**
315: * Sets the value of {@link $password} parameter.
316: * @param string $password
317: */
318: public function setPassword($password) {
319: $this->setString('password', $password, true);
320: }
321:
322: /**
323: * Returns the value of {@link $password} parameter.
324: * @return string
325: */
326: public function getPassword() {
327: return $this->password;
328: }
329:
330: /**
331: * Sets the value of {@link $timeout} parameter.
332: * @param int $timeout
333: */
334: public function setTimeout($timeout) {
335: $this->setPositiveInteger('timeout', $timeout);
336: }
337:
338: /**
339: * Returns the value of {@link $timeout} parameter.
340: * @return int
341: */
342: public function getTimeout() {
343: return $this->timeout;
344: }
345: /**
346: * Returns the mongodb connection.
347: * @return Mongo
348: */
349: public function getConnection() {
350: return $this->connection;
351: }
352:
353: /**
354: * Returns the active mongodb collection.
355: * @return MongoCollection
356: */
357: public function getCollection() {
358: return $this->collection;
359: }
360: }
361: