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 : * @package log4php
19 : */
20 :
21 : /**
22 : * Appends log events to a db table using PDO.
23 : *
24 : * Configurable parameters of this appender are:
25 : *
26 : * - user - Sets the user of this database connection
27 : * - password - Sets the password of this database connection
28 : * - createTable - true, if the table should be created if necessary. false otherwise
29 : * - table - Sets the table name (default: log4php_log)
30 : * - sql - Sets the insert statement for a logging event. Defaults
31 : * to the correct one - change only if you are sure what you are doing.
32 : * - dsn - Sets the DSN string for this connection
33 : *
34 : * If $sql is set then $table and $sql are used, else $table, $insertSql and $insertPattern.
35 : *
36 : * An example:
37 : *
38 : * {@example ../../examples/php/appender_pdo.php 19}
39 : *
40 : * {@example ../../examples/resources/appender_pdo.properties 18}
41 : *
42 : * @version $Revision: 806678 $
43 : * @package log4php
44 : * @subpackage appenders
45 : * @since 2.0
46 : */
47 : class LoggerAppenderPDO extends LoggerAppender {
48 :
49 : /**
50 : * Create the log table if it does not exists (optional).
51 : * @var string
52 : */
53 : protected $createTable = true;
54 :
55 : /**
56 : * Database user name.
57 : * @var string
58 : */
59 : protected $user;
60 :
61 : /**
62 : * Database password
63 : * @var string
64 : */
65 : protected $password;
66 :
67 : /**
68 : * DSN string for enabling a connection.
69 : * @var string
70 : */
71 : protected $dsn;
72 :
73 : /**
74 : * A {@link LoggerPatternLayout} string used to format a valid insert query.
75 : * @deprecated Use {@link $insertSql} and {@link $insertPattern} which properly handle quotes in the messages!
76 : * @var string
77 : */
78 : protected $sql;
79 :
80 : /**
81 : * Can be set to a complete insert statement with ? that are replaced using {@link insertPattern}.
82 : * @var string
83 : */
84 : protected $insertSql = "INSERT INTO __TABLE__ (timestamp, logger, level, message, thread, file, line) VALUES (?,?,?,?,?,?,?)";
85 :
86 : /**
87 : * A comma separated list of {@link LoggerPatternLayout} format strings that replace the "?" in {@link $sql}.
88 : * @var string
89 : */
90 : protected $insertPattern = "%d,%c,%p,%m,%t,%F,%L";
91 :
92 : /**
93 : * Table name to write events. Used only for CREATE TABLE if {@link $createTable} is true.
94 : * @var string
95 : */
96 : protected $table = 'log4php_log';
97 :
98 : /**
99 : * The PDO instance.
100 : * @var PDO
101 : */
102 : protected $db = null;
103 :
104 : /**
105 : * Prepared statement for the INSERT INTO query.
106 : * @var PDOStatement
107 : */
108 : protected $preparedInsert;
109 :
110 : /**
111 : * Set in activateOptions() and later used in append() to check if all conditions to append are true.
112 : * @var boolean
113 : */
114 : protected $canAppend = true;
115 :
116 : /**
117 : * This appender does not require a layout.
118 : */
119 : protected $requiresLayout = false;
120 :
121 : /**
122 : * Setup db connection.
123 : * Based on defined options, this method connects to db defined in {@link $dsn}
124 : * and creates a {@link $table} table if {@link $createTable} is true.
125 : * @return boolean true if all ok.
126 : * @throws a PDOException if the attempt to connect to the requested database fails.
127 : */
128 : public function activateOptions() {
129 : try {
130 5 : if($this->user === null) {
131 5 : $this->db = new PDO($this->dsn);
132 4 : } else if($this->password === null) {
133 0 : $this->db = new PDO($this->dsn, $this->user);
134 0 : } else {
135 0 : $this->db = new PDO($this->dsn,$this->user,$this->password);
136 : }
137 4 : $this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
138 :
139 : // test if log table exists
140 : try {
141 4 : $result = $this->db->query('SELECT * FROM ' . $this->table . ' WHERE 1 = 0');
142 0 : $result->closeCursor();
143 4 : } catch (PDOException $e) {
144 : // It could be something else but a "no such table" is the most likely
145 4 : $result = false;
146 : }
147 :
148 : // create table if necessary
149 4 : if ($result == false and $this->createTable) {
150 : // The syntax should at least be compatible with MySQL, PostgreSQL, SQLite and Oracle.
151 3 : $query = "CREATE TABLE {$this->table} (".
152 3 : "timestamp varchar(32)," .
153 3 : "logger varchar(64)," .
154 3 : "level varchar(32)," .
155 3 : "message varchar(9999)," .
156 3 : "thread varchar(32)," .
157 3 : "file varchar(255)," .
158 3 : "line varchar(6))";
159 3 : $result = $this->db->query($query);
160 3 : }
161 5 : } catch (PDOException $e) {
162 1 : $this->canAppend = false;
163 1 : throw new LoggerException($e);
164 : }
165 :
166 4 : $this->layout = new LoggerLayoutPattern();
167 :
168 : //
169 : // Keep compatibility to legacy option $sql which already included the format patterns!
170 : //
171 4 : if (empty($this->sql)) {
172 : // new style with prepared Statment and $insertSql and $insertPattern
173 : // Maybe the tablename has to be substituted.
174 3 : $this->insertSql = preg_replace('/__TABLE__/', $this->table, $this->insertSql);
175 3 : $this->preparedInsert = $this->db->prepare($this->insertSql);
176 3 : $this->layout->setConversionPattern($this->insertPattern);
177 3 : } else {
178 : // Old style with format strings in the $sql query should be used.
179 1 : $this->layout->setConversionPattern($this->sql);
180 : }
181 :
182 4 : $this->canAppend = true;
183 4 : return true;
184 : }
185 :
186 : /**
187 : * Appends a new event to the database.
188 : *
189 : * @throws LoggerException If the pattern conversion or the INSERT statement fails.
190 : */
191 : public function append(LoggerLoggingEvent $event) {
192 : // TODO: Can't activateOptions() simply throw an Exception if it encounters problems?
193 4 : if ( ! $this->canAppend) return;
194 :
195 : try {
196 4 : if (empty($this->sql)) {
197 : // new style with prepared statement
198 3 : $params = $this->layout->formatToArray($event);
199 3 : $this->preparedInsert->execute($params);
200 3 : } else {
201 : // old style
202 1 : $query = $this->layout->format($event);
203 1 : $this->db->exec($query);
204 : }
205 4 : } catch (Exception $e) {
206 0 : throw new LoggerException($e);
207 : }
208 4 : }
209 :
210 : /**
211 : * Closes the connection to the logging database
212 : */
213 : public function close() {
214 2 : if($this->closed != true) {
215 2 : if ($this->db !== null) {
216 2 : $this->db = null;
217 2 : }
218 2 : $this->closed = true;
219 2 : }
220 2 : }
221 :
222 : /**
223 : * Sets the username for this connection.
224 : * Defaults to ''
225 : */
226 : public function setUser($user) {
227 0 : $this->setString('user', $user);
228 0 : }
229 :
230 : /**
231 : * Sets the password for this connection.
232 : * Defaults to ''
233 : */
234 : public function setPassword($password) {
235 0 : $this->setString('password', $password);
236 0 : }
237 :
238 : /**
239 : * Indicator if the logging table should be created on startup,
240 : * if its not existing.
241 : */
242 : public function setCreateTable($flag) {
243 2 : $this->setBoolean('createTable', $flag);
244 2 : }
245 :
246 : /**
247 : * Sets the SQL string into which the event should be transformed.
248 : * Defaults to:
249 : *
250 : * INSERT INTO $this->table
251 : * ( timestamp, logger, level, message, thread, file, line)
252 : * VALUES
253 : * ('%d','%c','%p','%m','%t','%F','%L')
254 : *
255 : * It's not necessary to change this except you have customized logging'
256 : *
257 : * @deprecated See {@link setInsertSql} and {@link setInsertPattern}.
258 : */
259 : public function setSql($sql) {
260 1 : $this->setString('sql', $sql);
261 1 : }
262 :
263 : /**
264 : * Sets the SQL INSERT string to use with {@link $insertPattern}.
265 : *
266 : * @param $sql A complete INSERT INTO query with "?" that gets replaced.
267 : */
268 : public function setInsertSql($sql) {
269 1 : $this->setString('insertSql', $sql);
270 1 : }
271 :
272 : /**
273 : * Sets the {@link LoggerLayoutPattern} format strings for {@link $insertSql}.
274 : *
275 : * It's not necessary to change this except you have customized logging.
276 : *
277 : * @param $pattern Comma separated format strings like "%p,%m,%C"
278 : */
279 : public function setInsertPattern($pattern) {
280 1 : $this->setString('insertPattern', $pattern);
281 1 : }
282 :
283 : /**
284 : * Sets the tablename to which this appender should log.
285 : * Defaults to log4php_log
286 : */
287 : public function setTable($table) {
288 1 : $this->setString('table', $table);
289 1 : }
290 :
291 : /**
292 : * Sets the DSN string for this connection. In case of
293 : * SQLite it could look like this: 'sqlite:appenders/pdotest.sqlite'
294 : */
295 : public function setDSN($dsn) {
296 5 : $this->setString('dsn', $dsn);
297 5 : }
298 :
299 : /**
300 : * Sometimes databases allow only one connection to themselves in one thread.
301 : * SQLite has this behaviour. In that case this handle is needed if the database
302 : * must be checked for events.
303 : *
304 : * @return PDO
305 : */
306 : public function getDatabaseHandle() {
307 1 : return $this->db;
308 : }
309 : }
310 :
|