Files
openlayers/nicer-api-docs/closure-library/closure/goog/gears/logstore.js
2014-05-06 13:02:46 -05:00

479 lines
12 KiB
JavaScript

// Copyright 2008 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview This file implements a store for goog.debug.Logger data.
*/
goog.provide('goog.gears.LogStore');
goog.provide('goog.gears.LogStore.Query');
goog.require('goog.async.Delay');
goog.require('goog.debug.LogManager');
goog.require('goog.gears.BaseStore');
goog.require('goog.gears.BaseStore.SchemaType');
goog.require('goog.json');
goog.require('goog.log');
goog.require('goog.log.Level');
goog.require('goog.log.LogRecord');
/**
* Implements a store for goog.debug.Logger data.
* @param {goog.gears.Database} database Database.
* @param {?string=} opt_tableName Name of logging table to use.
* @extends {goog.gears.BaseStore}
* @constructor
*/
goog.gears.LogStore = function(database, opt_tableName) {
goog.gears.BaseStore.call(this, database);
/**
* Name of log table.
* @type {string}
*/
var tableName = opt_tableName || goog.gears.LogStore.DEFAULT_TABLE_NAME_;
this.tableName_ = tableName;
// Override BaseStore schema attribute.
this.schema = [
{
type: goog.gears.BaseStore.SchemaType.TABLE,
name: tableName,
columns: [
// Unique ID.
'id INTEGER PRIMARY KEY AUTOINCREMENT',
// Timestamp.
'millis BIGINT',
// #goog.log.Level value.
'level INTEGER',
// Message.
'msg TEXT',
// Name of logger object.
'logger TEXT',
// Serialized error object.
'exception TEXT',
// Full exception text.
'exceptionText TEXT'
]
},
{
type: goog.gears.BaseStore.SchemaType.INDEX,
name: tableName + 'MillisIndex',
isUnique: false,
tableName: tableName,
columns: ['millis']
},
{
type: goog.gears.BaseStore.SchemaType.INDEX,
name: tableName + 'LevelIndex',
isUnique: false,
tableName: tableName,
columns: ['level']
}
];
/**
* Buffered log records not yet flushed to DB.
* @type {Array.<goog.log.LogRecord>}
* @private
*/
this.records_ = [];
/**
* Save the publish handler so it can be removed.
* @type {Function}
* @private
*/
this.publishHandler_ = goog.bind(this.addLogRecord, this);
};
goog.inherits(goog.gears.LogStore, goog.gears.BaseStore);
/** @override */
goog.gears.LogStore.prototype.version = 1;
/**
* Whether we are currently capturing logger output.
* @type {boolean}
* @private
*/
goog.gears.LogStore.prototype.isCapturing_ = false;
/**
* Size of buffered log data messages.
* @type {number}
* @private
*/
goog.gears.LogStore.prototype.bufferSize_ = 0;
/**
* Scheduler for pruning action.
* @type {goog.async.Delay?}
* @private
*/
goog.gears.LogStore.prototype.delay_ = null;
/**
* Use this to protect against recursive flushing.
* @type {boolean}
* @private
*/
goog.gears.LogStore.prototype.isFlushing_ = false;
/**
* Logger.
* @type {goog.log.Logger}
* @private
*/
goog.gears.LogStore.prototype.logger_ =
goog.log.getLogger('goog.gears.LogStore');
/**
* Default value for how many records we keep when pruning.
* @type {number}
* @private
*/
goog.gears.LogStore.DEFAULT_PRUNE_KEEPER_COUNT_ = 1000;
/**
* Default value for how often to auto-prune (10 minutes).
* @type {number}
* @private
*/
goog.gears.LogStore.DEFAULT_AUTOPRUNE_INTERVAL_MILLIS_ = 10 * 60 * 1000;
/**
* The name for the log table.
* @type {string}
* @private
*/
goog.gears.LogStore.DEFAULT_TABLE_NAME_ = 'GoogGearsDebugLogStore';
/**
* Max message bytes to buffer before flushing to database.
* @type {number}
* @private
*/
goog.gears.LogStore.MAX_BUFFER_BYTES_ = 200000;
/**
* Flush buffered log records.
*/
goog.gears.LogStore.prototype.flush = function() {
if (this.isFlushing_ || !this.getDatabaseInternal()) {
return;
}
this.isFlushing_ = true;
// Grab local copy of records so database can log during this process.
goog.log.info(this.logger_, 'flushing ' + this.records_.length + ' records');
var records = this.records_;
this.records_ = [];
for (var i = 0; i < records.length; i++) {
var record = records[i];
var exception = record.getException();
var serializedException = exception ? goog.json.serialize(exception) : '';
var statement = 'INSERT INTO ' + this.tableName_ +
' (millis, level, msg, logger, exception, exceptionText)' +
' VALUES (?, ?, ?, ?, ?, ?)';
this.getDatabaseInternal().execute(statement,
record.getMillis(), record.getLevel().value, record.getMessage(),
record.getLoggerName(), serializedException,
record.getExceptionText() || '');
}
this.isFlushing_ = false;
};
/**
* Create new delay object for auto-pruning. Does not stop or
* start auto-pruning, call #startAutoPrune and #startAutoPrune for that.
* @param {?number=} opt_count Number of records of recent hitory to keep.
* @param {?number=} opt_interval Milliseconds to wait before next pruning.
*/
goog.gears.LogStore.prototype.createAutoPruneDelay = function(
opt_count, opt_interval) {
if (this.delay_) {
this.delay_.dispose();
this.delay_ = null;
}
var interval = typeof opt_interval == 'number' ?
opt_interval : goog.gears.LogStore.DEFAULT_AUTOPRUNE_INTERVAL_MILLIS_;
var listener = goog.bind(this.autoPrune_, this, opt_count);
this.delay_ = new goog.async.Delay(listener, interval);
};
/**
* Enable periodic pruning. As a side effect, this also flushes the memory
* buffer.
*/
goog.gears.LogStore.prototype.startAutoPrune = function() {
if (!this.delay_) {
this.createAutoPruneDelay(
goog.gears.LogStore.DEFAULT_PRUNE_KEEPER_COUNT_,
goog.gears.LogStore.DEFAULT_AUTOPRUNE_INTERVAL_MILLIS_);
}
this.delay_.fire();
};
/**
* Disable scheduled pruning.
*/
goog.gears.LogStore.prototype.stopAutoPrune = function() {
if (this.delay_) {
this.delay_.stop();
}
};
/**
* @return {boolean} True iff auto prune timer is active.
*/
goog.gears.LogStore.prototype.isAutoPruneActive = function() {
return !!this.delay_ && this.delay_.isActive();
};
/**
* Prune, and schedule next pruning.
* @param {?number=} opt_count Number of records of recent hitory to keep.
* @private
*/
goog.gears.LogStore.prototype.autoPrune_ = function(opt_count) {
this.pruneBeforeCount(opt_count);
this.delay_.start();
};
/**
* Keep some number of most recent log records and delete all older ones.
* @param {?number=} opt_count Number of records of recent history to keep. If
* unspecified, we use #goog.gears.LogStore.DEFAULT_PRUNE_KEEPER_COUNT_.
* Pass in 0 to delete all log records.
*/
goog.gears.LogStore.prototype.pruneBeforeCount = function(opt_count) {
if (!this.getDatabaseInternal()) {
return;
}
var count = typeof opt_count == 'number' ?
opt_count : goog.gears.LogStore.DEFAULT_PRUNE_KEEPER_COUNT_;
goog.log.info(this.logger_, 'pruning before ' + count + ' records ago');
this.flush();
this.getDatabaseInternal().execute('DELETE FROM ' + this.tableName_ +
' WHERE id <= ((SELECT MAX(id) FROM ' + this.tableName_ + ') - ?)',
count);
};
/**
* Delete log record #id and all older records.
* @param {number} sequenceNumber ID before which we delete all records.
*/
goog.gears.LogStore.prototype.pruneBeforeSequenceNumber =
function(sequenceNumber) {
if (!this.getDatabaseInternal()) {
return;
}
goog.log.info(this.logger_,
'pruning before sequence number ' + sequenceNumber);
this.flush();
this.getDatabaseInternal().execute(
'DELETE FROM ' + this.tableName_ + ' WHERE id <= ?',
sequenceNumber);
};
/**
* Whether we are currently capturing logger output.
* @return {boolean} Whether we are currently capturing logger output.
*/
goog.gears.LogStore.prototype.isCapturing = function() {
return this.isCapturing_;
};
/**
* Sets whether we are currently capturing logger output.
* @param {boolean} capturing Whether to capture logger output.
*/
goog.gears.LogStore.prototype.setCapturing = function(capturing) {
if (capturing != this.isCapturing_) {
this.isCapturing_ = capturing;
// Attach or detach handler from the root logger.
var rootLogger = goog.debug.LogManager.getRoot();
if (capturing) {
goog.log.addHandler(rootLogger, this.publishHandler_);
goog.log.info(this.logger_, 'enabled');
} else {
goog.log.info(this.logger_, 'disabling');
goog.log.removeHandler(rootLogger, this.publishHandler_);
}
}
};
/**
* Adds a log record.
* @param {goog.log.LogRecord} logRecord the LogRecord.
*/
goog.gears.LogStore.prototype.addLogRecord = function(logRecord) {
this.records_.push(logRecord);
this.bufferSize_ += logRecord.getMessage().length;
var exceptionText = logRecord.getExceptionText();
if (exceptionText) {
this.bufferSize_ += exceptionText.length;
}
if (this.bufferSize_ >= goog.gears.LogStore.MAX_BUFFER_BYTES_) {
this.flush();
}
};
/**
* Select log records.
* @param {goog.gears.LogStore.Query} query Query object.
* @return {Array.<goog.log.LogRecord>} Selected logs in descending
* order of creation time.
*/
goog.gears.LogStore.prototype.select = function(query) {
if (!this.getDatabaseInternal()) {
// This should only occur if we've been disposed.
return [];
}
this.flush();
// TODO(user) Perhaps have Query object build this SQL string so we can
// omit unneeded WHERE clauses.
var statement =
'SELECT id, millis, level, msg, logger, exception, exceptionText' +
' FROM ' + this.tableName_ +
' WHERE level >= ? AND millis >= ? AND millis <= ?' +
' AND msg like ? and logger like ?' +
' ORDER BY id DESC LIMIT ?';
var rows = this.getDatabaseInternal().queryObjectArray(statement,
query.level.value, query.minMillis, query.maxMillis,
query.msgLike, query.loggerLike, query.limit);
var result = Array(rows.length);
for (var i = rows.length - 1; i >= 0; i--) {
var row = rows[i];
// Parse fields, allowing for invalid values.
var sequenceNumber = Number(row['id']) || 0;
var level = goog.log.Level.getPredefinedLevelByValue(
Number(row['level']) || 0);
var msg = row['msg'] || '';
var loggerName = row['logger'] || '';
var millis = Number(row['millis']) || 0;
var serializedException = row['exception'];
var exception = serializedException ?
goog.json.parse(serializedException) : null;
var exceptionText = row['exceptionText'] || '';
// Create record.
var record = new goog.log.LogRecord(level, msg, loggerName,
millis, sequenceNumber);
if (exception) {
record.setException(exception);
record.setExceptionText(exceptionText);
}
result[i] = record;
}
return result;
};
/** @override */
goog.gears.LogStore.prototype.disposeInternal = function() {
this.flush();
goog.gears.LogStore.superClass_.disposeInternal.call(this);
if (this.delay_) {
this.delay_.dispose();
this.delay_ = null;
}
};
/**
* Query to select log records.
* @constructor
*/
goog.gears.LogStore.Query = function() {
};
/**
* Minimum logging level.
* @type {goog.log.Level}
*/
goog.gears.LogStore.Query.prototype.level = goog.log.Level.ALL;
/**
* Minimum timestamp, inclusive.
* @type {number}
*/
goog.gears.LogStore.Query.prototype.minMillis = -1;
/**
* Maximum timestamp, inclusive.
* @type {number}
*/
goog.gears.LogStore.Query.prototype.maxMillis = Infinity;
/**
* Message 'like' pattern.
* See http://www.sqlite.org/lang_expr.html#likeFunc for 'like' syntax.
* @type {string}
*/
goog.gears.LogStore.Query.prototype.msgLike = '%';
/**
* Logger name 'like' pattern.
* See http://www.sqlite.org/lang_expr.html#likeFunc for 'like' syntax.
* @type {string}
*/
goog.gears.LogStore.Query.prototype.loggerLike = '%';
/**
* Max # recent records to return. -1 means no limit.
* @type {number}
*/
goog.gears.LogStore.Query.prototype.limit = -1;