simple-tracker
Version:
Easy client-side tracking library to log events, metrics, errors, and messages
273 lines (233 loc) • 7.69 kB
JavaScript
/*! simple-tracker | MIT *
* https://github.com/codeniko/simple-tracker !*/
(function() {
'use strict'
function simpleTracker(window) {
var SESSION_KEY = 'trcksesh'
var SESSION_KEY_LENGTH = SESSION_KEY.length + 1
var document = window.document
var sendCaughtExceptions = false
var attachClientContext = true
var attachSessionId = true
var devMode = false
var endpoint
var sessionId
var tracker
var timer = {}
function uuidv4(a) {
return a?(a^Math.random()*16>>a/4).toString(16):([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g,uuidv4)
}
// override to call own onerror function, followed by original onerror
function setOnError(f) {
var _onerror = window.onerror
// msg, url, line, col, err
window.onerror = function() {
f.apply(tracker, arguments)
if (typeof _onerror === 'function') {
_onerror.apply(window, arguments)
}
}
}
function getClientContext() {
return {
url: window.location.href,
userAgent: window.navigator.userAgent || null,
platform: window.navigator.platform || null
}
}
function readCookie() {
var cookie = document.cookie
var i = cookie.indexOf(SESSION_KEY)
if (i >= 0) {
var end = cookie.indexOf(';', i + 1)
end = end < 0 ? cookie.length : end
return cookie.slice(i + SESSION_KEY_LENGTH, end)
}
}
function setCookie(value) {
document.cookie = SESSION_KEY + '=' + value
}
function setSession(newSessionId) {
sessionId = newSessionId || sessionId || readCookie() || uuidv4()
setCookie(sessionId)
}
function track(data) {
if (endpoint && Object.keys(data).length > 0) {
if (attachSessionId) {
data.sessionId = sessionId
}
if (attachClientContext) {
data.context = tracker.clientContext
}
if (!devMode) {
try {
// let's not use fetch to avoid a polyfill
var xmlHttp = new window.XMLHttpRequest()
xmlHttp.open('POST', endpoint, true) // true for async
xmlHttp.setRequestHeader('Content-Type', 'application/json')
xmlHttp.send(JSON.stringify(data))
} catch(ex) { }
} else {
console.debug('SimpleTracker: POST ' + endpoint, data)
}
}
}
function SimpleTracker() {
this.clientContext = getClientContext()
}
SimpleTracker.prototype = {
// accessible to those have this tracker object so they can create their own onerror functions and still able to invoke default onerror behavior for our tracker.
onerror: function(msg, url, line, col, err) {
var exception = {
message: msg,
lineno: line,
colno: col,
stack: err ? err.stack : 'n/a'
}
this.logException(exception)
},
logEvent: function(event, additionalParams) {
var data = {
type: 'event',
event: event
}
// if additional params defined, copy them over
if (typeof additionalParams === 'object') {
for (var prop in additionalParams) {
if (additionalParams.hasOwnProperty(prop)) {
data[prop] = additionalParams[prop]
}
}
}
this.push(data)
},
logException: function(exception) {
this.push({
level: 'error',
type: 'exception',
exception: exception
})
},
logMessage: function(message, level) {
var data = {
type: 'message',
message: message
}
// add optional level if defined, not included otherwise
if (level) {
data.level = level
}
this.push(data)
},
logMetric: function(metric, value) {
this.push({
type: 'metric',
metric: metric,
value: value
})
},
startTimer: function(metric) {
var performance = window.performance
if (performance.now) {
/* istanbul ignore if */
if (timer[metric] && devMode) {
console.warn("Timing metric '" + metric + "' already started")
}
devMode && console.debug('timer started for:', metric)
timer[metric] = performance.now()
}
},
stopTimer: function(metric) {
var performance = window.performance
if (performance.now) {
var stopTime = performance.now()
var startTime = timer[metric]
/* istanbul ignore else */
if (startTime !== undefined) {
var diff = Math.round(stopTime - startTime)
timer[metric] = undefined
devMode && console.debug('timer stopped for:', metric, 'time=' + diff)
this.logMetric(metric, diff)
} else {
devMode && console.warn("Timing metric '" + metric + "' wasn't started")
}
}
},
push: function(data) {
var type = typeof data
if (type !== 'object' && type !== 'string') {
return
}
if (type === 'string') {
data = {
text: data
}
} else {
// toggle devmode, where requests wont be sent, but logged in console for debugging instead
if (data.devMode !== undefined) {
devMode = !!data.devMode
delete data.devMode
}
if (data.attachClientContext !== undefined) {
attachClientContext = !!data.attachClientContext
delete data.attachClientContext
}
if (data.attachSessionId !== undefined) {
attachSessionId = !!data.attachSessionId
delete data.attachSessionId
}
if (data.sessionId) {
setSession(data.sessionId)
delete data.sessionId
}
if (data.endpoint) {
// other initializations should go here since endpoint is the only required property that needs to be set
if (!sessionId) {
setSession()
}
endpoint = data.endpoint
delete data.endpoint
}
if (data.sendCaughtExceptions !== undefined) {
sendCaughtExceptions = !!data.sendCaughtExceptions
if (sendCaughtExceptions) {
setOnError(this.onerror)
}
delete data.sendCaughtExceptions
}
}
track(data)
}
}
var existingTracker = window.tracker // either instance of SimpleTracker or existing array of events to log that were added while this script was being loaded asyncronously
if (existingTracker && existingTracker.length) {
// move all from existing and push into our tracker object
tracker = new SimpleTracker()
var i = 0
var length = existingTracker.length
for (i = 0; i < length; i++) {
tracker.push(existingTracker[i])
}
} else {
tracker = new SimpleTracker()
}
window.tracker = tracker
window.SimpleTracker = SimpleTracker
return tracker
}
var isModule = typeof module !== 'undefined' && module.exports
/* istanbul ignore else */
if (typeof window !== 'undefined') {
var tracker = simpleTracker(window) // sets window.tracker
if (isModule) {
simpleTracker.default = tracker
module.exports = tracker
}
} else if (isModule) {
// for testing
simpleTracker.default = simpleTracker
module.exports = simpleTracker
} else {
throw new Error('')
}
})()