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: * LoggerAppenderPDO appender logs to a database using the PHP's PDO extension.
21: *
22: * ## Configurable parameters: ##
23: *
24: * - dsn - The Data Source Name (DSN) used to connect to the database.
25: * - user - Username used to connect to the database.
26: * - password - Password used to connect to the database.
27: * - table - Name of the table to which log entries are be inserted.
28: * - insertSQL - Sets the insert statement for a logging event. Defaults
29: * to the correct one - change only if you are sure what you are doing.
30: * - insertPattern - The conversion pattern to use in conjuction with insert
31: * SQL. Must contain the same number of comma separated
32: * conversion patterns as there are question marks in the
33: * insertSQL.
34: *
35: * @version $Revision: 1374546 $
36: * @package log4php
37: * @subpackage appenders
38: * @since 2.0
39: * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
40: * @link http://logging.apache.org/log4php/docs/appenders/pdo.html Appender documentation
41: */
42: class LoggerAppenderPDO extends LoggerAppender {
43:
44: // ******************************************
45: // *** Configurable parameters ***
46: // ******************************************
47:
48: /**
49: * DSN string used to connect to the database.
50: * @see http://www.php.net/manual/en/pdo.construct.php
51: */
52: protected $dsn;
53:
54: /** Database user name. */
55: protected $user;
56:
57: /** Database password. */
58: protected $password;
59:
60: /**
61: * The insert query.
62: *
63: * The __TABLE__ placeholder will be replaced by the table name from
64: * {@link $table}.
65: *
66: * The questionmarks are part of the prepared statement, and they must
67: * match the number of conversion specifiers in {@link insertPattern}.
68: */
69: protected $insertSQL = "INSERT INTO __TABLE__ (timestamp, logger, level, message, thread, file, line) VALUES (?, ?, ?, ?, ?, ?, ?)";
70:
71: /**
72: * A comma separated list of {@link LoggerPatternLayout} format strings
73: * which replace the "?" in {@link $insertSQL}.
74: *
75: * Must contain the same number of comma separated conversion patterns as
76: * there are question marks in {@link insertSQL}.
77: *
78: * @see LoggerPatternLayout For conversion patterns.
79: */
80: protected $insertPattern = "%date{Y-m-d H:i:s},%logger,%level,%message,%pid,%file,%line";
81:
82: /** Name of the table to which to append log events. */
83: protected $table = 'log4php_log';
84:
85: /** The number of recconect attempts to make on failed append. */
86: protected $reconnectAttempts = 3;
87:
88:
89: // ******************************************
90: // *** Private memebers ***
91: // ******************************************
92:
93: /**
94: * The PDO instance.
95: * @var PDO
96: */
97: protected $db;
98:
99: /**
100: * Prepared statement for the insert query.
101: * @var PDOStatement
102: */
103: protected $preparedInsert;
104:
105: /** This appender does not require a layout. */
106: protected $requiresLayout = false;
107:
108:
109: // ******************************************
110: // *** Appender methods ***
111: // ******************************************
112:
113: /**
114: * Acquires a database connection based on parameters.
115: * Parses the insert pattern to create a chain of converters which will be
116: * used in forming query parameters from logging events.
117: */
118: public function activateOptions() {
119: try {
120: $this->establishConnection();
121: } catch (PDOException $e) {
122: $this->warn("Failed connecting to database. Closing appender. Error: " . $e->getMessage());
123: $this->close();
124: return;
125: }
126:
127: // Parse the insert patterns; pattern parts are comma delimited
128: $pieces = explode(',', $this->insertPattern);
129: $converterMap = LoggerLayoutPattern::getDefaultConverterMap();
130: foreach($pieces as $pattern) {
131: $parser = new LoggerPatternParser($pattern, $converterMap);
132: $this->converters[] = $parser->parse();
133: }
134:
135: $this->closed = false;
136: }
137:
138: /**
139: * Connects to the database, and prepares the insert query.
140: * @throws PDOException If connect or prepare fails.
141: */
142: protected function establishConnection() {
143: // Acquire database connection
144: $this->db = new PDO($this->dsn, $this->user, $this->password);
145: $this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
146:
147: // Prepare the insert statement
148: $insertSQL = str_replace('__TABLE__', $this->table, $this->insertSQL);
149: $this->preparedInsert = $this->db->prepare($insertSQL);
150: }
151:
152: /**
153: * Appends a new event to the database.
154: *
155: * If writing to database fails, it will retry by re-establishing the
156: * connection up to $reconnectAttempts times. If writing still fails,
157: * the appender will close.
158: */
159: public function append(LoggerLoggingEvent $event) {
160:
161: for ($attempt = 1; $attempt <= $this->reconnectAttempts + 1; $attempt++) {
162: try {
163: // Attempt to write to database
164: $this->preparedInsert->execute($this->format($event));
165: $this->preparedInsert->closeCursor();
166: break;
167: } catch (PDOException $e) {
168: $this->warn("Failed writing to database: ". $e->getMessage());
169:
170: // Close the appender if it's the last attempt
171: if ($attempt > $this->reconnectAttempts) {
172: $this->warn("Failed writing to database after {$this->reconnectAttempts} reconnect attempts. Closing appender.");
173: $this->close();
174: // Otherwise reconnect and try to write again
175: } else {
176: $this->warn("Attempting a reconnect (attempt $attempt of {$this->reconnectAttempts}).");
177: $this->establishConnection();
178: }
179: }
180: }
181: }
182:
183: /**
184: * Converts the logging event to a series of database parameters by using
185: * the converter chain which was set up on activation.
186: */
187: protected function format(LoggerLoggingEvent $event) {
188: $params = array();
189: foreach($this->converters as $converter) {
190: $buffer = '';
191: while ($converter !== null) {
192: $converter->format($buffer, $event);
193: $converter = $converter->next;
194: }
195: $params[] = $buffer;
196: }
197: return $params;
198: }
199:
200: /**
201: * Closes the connection to the logging database
202: */
203: public function close() {
204: // Close the connection (if any)
205: $this->db = null;
206:
207: // Close the appender
208: $this->closed = true;
209: }
210:
211: // ******************************************
212: // *** Accessor methods ***
213: // ******************************************
214:
215: /**
216: * Returns the active database handle or null if not established.
217: * @return PDO
218: */
219: public function getDatabaseHandle() {
220: return $this->db;
221: }
222:
223: /** Sets the username. */
224: public function setUser($user) {
225: $this->setString('user', $user);
226: }
227:
228: /** Returns the username. */
229: public function getUser($user) {
230: return $this->user;
231: }
232:
233: /** Sets the password. */
234: public function setPassword($password) {
235: $this->setString('password', $password);
236: }
237:
238: /** Returns the password. */
239: public function getPassword($password) {
240: return $this->password;
241: }
242:
243: /** Sets the insert SQL. */
244: public function setInsertSQL($sql) {
245: $this->setString('insertSQL', $sql);
246: }
247:
248: /** Returns the insert SQL. */
249: public function getInsertSQL($sql) {
250: return $this->insertSQL;
251: }
252:
253: /** Sets the insert pattern. */
254: public function setInsertPattern($pattern) {
255: $this->setString('insertPattern', $pattern);
256: }
257:
258: /** Returns the insert pattern. */
259: public function getInsertPattern($pattern) {
260: return $this->insertPattern;
261: }
262:
263: /** Sets the table name. */
264: public function setTable($table) {
265: $this->setString('table', $table);
266: }
267:
268: /** Returns the table name. */
269: public function getTable($table) {
270: return $this->table;
271: }
272:
273: /** Sets the DSN string. */
274: public function setDSN($dsn) {
275: $this->setString('dsn', $dsn);
276: }
277:
278: /** Returns the DSN string. */
279: public function getDSN($dsn) {
280: return $this->setString('dsn', $dsn);
281: }
282: }
283: