      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: