├── .gitignore ├── .snyk ├── .github └── repository_metadata.yml ├── deps ├── console-extern.js └── umd-extern.js ├── src ├── README.md └── le.js ├── cortex.yaml ├── component.json ├── bower.json ├── product ├── README.md ├── le.min.js ├── le.js └── le.dyn.js ├── .travis.yml ├── README.md ├── LICENSE ├── package.json ├── gulpfile.js ├── karma.conf-ci.js ├── karma.conf.js └── test └── leSpec.js /.gitignore: -------------------------------------------------------------------------------- 1 | lib/* 2 | build/* 3 | node_modules/ 4 | test/live-client.html -------------------------------------------------------------------------------- /.snyk: -------------------------------------------------------------------------------- 1 | version: v1.25.0 2 | ignore: {} 3 | patch: {} 4 | exclude: {} 5 | -------------------------------------------------------------------------------- /.github/repository_metadata.yml: -------------------------------------------------------------------------------- 1 | 2 | repo_owners: 3 | - dublin-splinter 4 | -------------------------------------------------------------------------------- /deps/console-extern.js: -------------------------------------------------------------------------------- 1 | var console = {}; 2 | /** 3 | * @param {string} message 4 | */ 5 | console.warn = function(message) {}; 6 | console.error = function(message) {}; 7 | -------------------------------------------------------------------------------- /src/README.md: -------------------------------------------------------------------------------- 1 | le.js source 2 | ============ 3 | 4 | Don't use any code from here on your site! 5 | 6 | Use production builds from [here](https://github.com/logentries/le_js/tree/master/product) instead. -------------------------------------------------------------------------------- /deps/umd-extern.js: -------------------------------------------------------------------------------- 1 | // CommonJS 2 | var module = { 3 | exports: null 4 | }; 5 | var exports = null; 6 | 7 | // CommonJS/Browserify 8 | var global = null; 9 | 10 | // AMD 11 | var define = function(require, module){}; 12 | define.amd = true; -------------------------------------------------------------------------------- /cortex.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | info: 3 | title: Le Js 4 | description: Client-side JavaScript logging library for Logentries 5 | x-cortex-git: 6 | github: 7 | alias: r7org 8 | repository: rapid7/le_js 9 | x-cortex-tag: le-js 10 | x-cortex-type: service 11 | x-cortex-domain-parents: 12 | - tag: logsearch 13 | openapi: 3.0.1 14 | servers: 15 | - url: "/" 16 | -------------------------------------------------------------------------------- /component.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "le_js", 3 | "repo": "logentries/le_js", 4 | "description": "CLient-side JavaScript logging library for Logentries", 5 | "version": "0.0.3", 6 | "keywords": [], 7 | "dependencies": {}, 8 | "development": {}, 9 | "license": "MIT", 10 | "main": "product/le.common.js", 11 | "scripts": [ 12 | "product/le.common.js" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "le_js", 3 | "version": "0.0.3", 4 | "homepage": "https://logentries.com", 5 | "description": "Javascript library for Logentries.com", 6 | "main": "product/le.js", 7 | "license": "MIT", 8 | "ignore": [ 9 | "**/.*", 10 | "bower_components", 11 | "build", 12 | "deps", 13 | "dyn.xhr", 14 | "lib", 15 | "node_modules", 16 | "src", 17 | "test", 18 | "gulpfile.js" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /product/README.md: -------------------------------------------------------------------------------- 1 | le.js 2 | ===== 3 | 4 | The files in this directory are auto-generated through `gulp build`. If you alter them by hand, expect them to be overwritten the next time you run the task! 5 | 6 | Contents 7 | -------- 8 | 9 | Several files are produced in the build. You'll probably just want to use __le.min.js__, however. 10 | 11 | * __le.js__: JS client library, unminified 12 | * __le.min.js__: JS client library, minified 13 | * __le.dyn.js__: JS client library with dyn.js compatibility 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.1 4 | sudo: false 5 | deploy: 6 | provider: releases 7 | api_key: 8 | secure: S2JVfYhWj4b2DvjsPtJYqUrAUtC9Mwdc1kMkA+p/LeSYJOZL8gFq55YReAPI1pNKSTJxwnxdlbMb5gtxyItVj7vz23Inv5S1DwialwgTff3ZV1JOquT91BeHnvphjBdm5L8G65QI35xEu5d1AuFr+cv6q+ocGr/k70b56fnEPcs= 9 | file: product/le.min.js 10 | skip_cleanup: true 11 | on: 12 | repo: logentries/le_js 13 | tags: true 14 | env: 15 | global: 16 | - secure: AToXqsiOsYiCigEK0bmNBVwgQQ5fKk6+hJWWSL/A+1v+tWaa3HC7+0p3Mscy1PBqX1PeYqD7GGUUpECUGUzqLjsbXhSZQ11ECKVk1qCw18dVxE/rxKVsvTA1Ie0xOwsQvqwkxfRAVgYwN3D3smDuZXLRvASMdUfwEPAq5BXZhc0= 17 | - secure: Jk+2mrcxAzhnTtImz9hhDb/JLu4rCl2jR3KCZJUzH4OQMwYbWcy7BeZpnJFWgDoylohOTUQMpkpzLZfusbyDREJAgV+sSPEFYN1yOFIwM2t1RL1Ajl1m/eCsYWuHYcLXcRutMqVRrMW+6olA8GjUHPr/vK0gTNrZfBVXvtJL6BE= 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Using InsightOps? 2 | Please use our updated [library](https://github.com/rapid7/r7insight_js) which supports specifying region to send your log data to. 3 | 4 | le.js 5 | ===== 6 | 7 | Client-side JavaScript logging library for [Logentries](http://www.logentries.com). 8 | 9 | [![Build Status](https://travis-ci.org/logentries/le_js.png?branch=master)](https://travis-ci.org/logentries/le_js) 10 | 11 | Features 12 | -------- 13 | 14 | * Small: ~3k (minified) 15 | * Cross-browser compatible 16 | * No external dependencies 17 | * Simple API 18 | * AMD and CommonJS support 19 | 20 | Quick start 21 | ----------- 22 | 23 | Start [here](https://github.com/logentries/le_js/wiki/Getting-started), then check out the rest of the wiki. 24 | 25 | Don't have an account? Get one [for free](https://logentries.com/quick-start/). 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2015 Logentries 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "le_js", 3 | "version": "0.0.3", 4 | "description": "Javascript library for Logentries.com", 5 | "main": "product/le.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "karma start karma.conf-ci.js", 11 | "build": "gulp build", 12 | "localtest": "karma start; jshint src/ test/leSpec.js" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git://github.com/logentries/le_js.git" 17 | }, 18 | "author": "", 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/logentries/le_js/issues" 22 | }, 23 | "homepage": "https://logentries.com", 24 | "devDependencies": { 25 | "google-closure-compiler": "^20161024.2.0", 26 | "gulp": "^3.6.2", 27 | "gulp-concat": "^2.2.0", 28 | "gulp-karma": "^0.0.4", 29 | "gulp-rename": "^1.2.0", 30 | "gulp-replace": "^0.2.0", 31 | "jshint": "^2.5.10", 32 | "karma": "^0.12.16", 33 | "karma-cli": "0.0.4", 34 | "karma-jasmine": "^0.1.5", 35 | "karma-phantomjs-launcher": "^0.1.4", 36 | "karma-sauce-launcher": "^0.2.10", 37 | "karma-sinon": "^1.0.4", 38 | "sinon": "^1.12.1" 39 | }, 40 | "engines": { 41 | "node": "~0.10" 42 | }, 43 | "jshintConfig": { 44 | "undef": true, 45 | "es3": true 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var concat = require('gulp-concat'); 3 | var karma = require('gulp-karma'); 4 | var closureCompiler = require('google-closure-compiler').gulp(); 5 | var rename = require('gulp-rename'); 6 | var replace = require('gulp-replace'); 7 | 8 | var testFiles = [ 9 | 'src/le.js', 10 | 'test/sinon*.js', 11 | 'test/*Spec.js' 12 | ]; 13 | var apiVersion = 1; 14 | var apiEndpoint = 'js.logentries.com/v' + apiVersion; 15 | var webhookEndpoint = 'webhook.logentries.com/noformat'; 16 | 17 | 18 | gulp.task('default', ['test', 'build']); 19 | 20 | 21 | gulp.task('watch', function() { 22 | gulp.watch('src/le.js', ['test']); 23 | }); 24 | 25 | 26 | gulp.task('build', function() { 27 | return gulp.src('src/le.js') 28 | .pipe(concat('le.js')) // We've only got one file but still need this 29 | .pipe(replace(/localhost:8080\/v1/g, apiEndpoint)) 30 | .pipe(replace(/localhost:8080\/noformat/g, webhookEndpoint)) 31 | .pipe(gulp.dest('product')) 32 | .pipe(closureCompiler({ 33 | compilation_level: 'SIMPLE_OPTIMIZATIONS', 34 | warning_level: 'VERBOSE', 35 | debug: false, 36 | language_in: 'ECMASCRIPT5_STRICT', 37 | externs: 'deps/umd-extern.js' 38 | })) 39 | .pipe(rename('le.min.js')) 40 | .pipe(gulp.dest('product')); 41 | }); 42 | -------------------------------------------------------------------------------- /karma.conf-ci.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | 3 | module.exports = function(config) { 4 | 5 | // Browsers to run on Sauce Labs 6 | var customLaunchers = { 7 | 'SL_Chrome': { 8 | base: 'SauceLabs', 9 | browserName: 'chrome' 10 | } 11 | }; 12 | 13 | config.set({ 14 | 15 | // base path that will be used to resolve all patterns (eg. files, exclude) 16 | basePath: '', 17 | 18 | 19 | // frameworks to use 20 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 21 | frameworks: ['jasmine', 'sinon'], 22 | 23 | 24 | // list of files / patterns to load in the browser 25 | files: [ 26 | 'src/le.js', 27 | 'test/leSpec.js' 28 | ], 29 | 30 | 31 | // test results reporter to use 32 | // possible values: 'dots', 'progress' 33 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 34 | reporters: ['dots', 'saucelabs'], 35 | 36 | 37 | // web server port 38 | port: 9876, 39 | 40 | colors: true, 41 | 42 | // level of logging 43 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 44 | logLevel: config.LOG_INFO, 45 | 46 | sauceLabs: { 47 | username: process.env.SAUCELABS_USERNAME, 48 | accessKey: process.env.SAUCELABS_ACCESSKEY, 49 | testName: process.env.TRAVIS_JOB_NUMBER 50 | }, 51 | captureTimeout: 120000, 52 | customLaunchers: customLaunchers, 53 | 54 | // start these browsers 55 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 56 | browsers: Object.keys(customLaunchers), 57 | singleRun: true 58 | }); 59 | }; 60 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on Fri Apr 04 2014 16:41:37 GMT+0100 (IST) 3 | /*jslint node:true*/ 4 | 5 | module.exports = function(config) { 6 | config.set({ 7 | 8 | // base path that will be used to resolve all patterns (eg. files, exclude) 9 | basePath: '', 10 | 11 | 12 | // frameworks to use 13 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 14 | frameworks: ['jasmine', 'sinon'], 15 | 16 | 17 | // list of files / patterns to load in the browser 18 | files: [ 19 | // Source files 20 | 'src/*.js', 21 | // Specs 22 | 'test/*Spec.js' 23 | ], 24 | 25 | 26 | // list of files to exclude 27 | exclude: [ 28 | 29 | ], 30 | 31 | 32 | // preprocess matching files before serving them to the browser 33 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 34 | preprocessors: { 35 | 36 | }, 37 | 38 | 39 | // test results reporter to use 40 | // possible values: 'dots', 'progress' 41 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 42 | reporters: ['progress'], 43 | 44 | 45 | // web server port 46 | port: 9876, 47 | 48 | 49 | // enable / disable colors in the output (reporters and logs) 50 | colors: true, 51 | 52 | 53 | // level of logging 54 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 55 | logLevel: config.LOG_INFO, 56 | 57 | 58 | // enable / disable watching file and executing tests whenever any file changes 59 | autoWatch: true, 60 | 61 | 62 | // start these browsers 63 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 64 | browsers: ['PhantomJS'], 65 | 66 | 67 | // Continuous Integration mode 68 | // if true, Karma captures browsers, runs the tests and exits 69 | singleRun: false 70 | }); 71 | }; 72 | -------------------------------------------------------------------------------- /product/le.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2015 Logentries. 3 | Please view license at https://raw.github.com/logentries/le_js/master/LICENSE 4 | */ 5 | 'use strict';(function(b,e){"function"===typeof define&&define.amd?define(function(){return e(b)}):"object"===typeof exports?("object"===typeof global&&(b=global),module.exports=e(b)):b.LE=e(b)})(this,function(b){function e(a){var c=a.trace?(Math.random()+Math.PI).toString(36).substring(2,10):null,q=a.page_info,e=a.token,g=a.print,f=a.no_format,r;r="undefined"===typeof XDomainRequest?a.ssl:"https:"===b.location.protocol?!0:!1;var k;k=b.LEENDPOINT?b.LEENDPOINT:f?"webhook.logentries.com/noformat":"js.logentries.com/v1"; 6 | k=(r?"https://":"http://")+k+"/logs/"+e;var h=[],l=!1,s=!1;if(a.catchall){var t=b.onerror;b.onerror=function(a,b,d){m({error:a,line:d,location:b}).level("ERROR").send();return t?t(a,b,d):!1}}var p=function(){var a=b.navigator||{doNotTrack:void 0},c=b.screen||{};return{url:(b.location||{}).pathname,referrer:document.referrer,screen:{width:c.width,height:c.height},window:{width:b.innerWidth,height:b.innerHeight},browser:{name:a.appName,version:a.appVersion,cookie_enabled:a.cookieEnabled,do_not_track:a.doNotTrack}, 7 | platform:a.platform}},u=function(){var a=null,a=Array.prototype.slice.call(arguments);if(0===a.length)throw Error("No arguments!");return a=1===a.length?a[0]:a},m=function(a){var b=u.apply(this,arguments),d={event:b};"never"===q||s&&"per-entry"!==q||(s=!0,"undefined"===typeof b.screen&&"undefined"===typeof b.browser&&m(p()).level("PAGE").send());c&&(d.trace=c);return{level:function(a){if(g&&"undefined"!==typeof console&&"PAGE"!==a){var b=null;"undefined"!==typeof XDomainRequest&&(b=d.trace+" "+d.event); 8 | try{console[a.toLowerCase()].call(console,b||d)}catch(c){console.log(b||d)}}d.level=a;return{send:function(){var a=[],b=JSON.stringify(d,function(b,d){if("undefined"===typeof d)return"undefined";if("object"===typeof d&&null!==d){var c;a:{for(c=0;c";a.push(d)}return d});l?h.push(b):n(e,b)}}}}};this.log=m;var n=function(a,b){l=!0;var d;d="undefined"!==typeof XDomainRequest?new XDomainRequest:new XMLHttpRequest;d.constructor===XMLHttpRequest? 9 | d.onreadystatechange=function(){4===d.readyState&&(400<=d.status?(console.error("Couldn't submit events."),410===d.status&&console.warn("This version of le_js is no longer supported!")):(301===d.status&&console.warn("This version of le_js is deprecated! Consider upgrading."),0'); 165 | }); 166 | 167 | afterEach(restoreXMLHttpRequests); 168 | afterEach(destroy); 169 | }); 170 | 171 | describe('sending user agent data', function () { 172 | beforeEach(mockXMLHttpRequests); 173 | beforeEach(addGetJson); 174 | 175 | function checkAgentInfo(agent) { 176 | expect(agent).toBeDefined(); 177 | 178 | // Perhaps these could be filled in since we're running in a 179 | // real browser now? 180 | expect(agent.url).toBeDefined(); 181 | expect(agent.referrer).toBeDefined(); 182 | expect(agent.screen).toBeDefined(); 183 | expect(agent.window).toBeDefined(); 184 | expect(agent.browser).toBeDefined(); 185 | expect(agent.platform).toBeDefined(); 186 | } 187 | 188 | it('page_info: never - never sends log data', function () { 189 | LE.init({ 190 | token: TOKEN, 191 | page_info: 'never' 192 | }); 193 | 194 | LE.log('hi'); 195 | 196 | var data = this.getXhrJson(0); 197 | 198 | expect(data.event).toBe('hi'); 199 | expect(this.getXhrJson(0).agent).toBeUndefined(); 200 | }); 201 | 202 | it('page_info: per-entry - sends log data for each log', function () { 203 | LE.init({ 204 | token: TOKEN, 205 | page_info: 'per-entry' 206 | }); 207 | 208 | LE.log('hi'); 209 | 210 | // Check data is sent the first time 211 | checkAgentInfo(this.getXhrJson(0).event); 212 | 213 | // Respond to first request so that the 2nd request will be made 214 | this.requestList[0].respond(); 215 | 216 | expect(this.getXhrJson(1).event).toBe('hi'); 217 | 218 | LE.log('hi again'); 219 | this.requestList[1].respond(); 220 | 221 | // Check that page info is sent subsequent times 222 | checkAgentInfo(this.getXhrJson(2).event); 223 | 224 | this.requestList[2].respond(); 225 | 226 | expect(this.getXhrJson(3).event).toBe('hi again'); 227 | }); 228 | 229 | it('page_info: per-page - always sends data for each log', function () { 230 | LE.init({ 231 | token: TOKEN, 232 | page_info: 'per-page' 233 | }); 234 | 235 | LE.log('hi'); 236 | 237 | // Check data is sent the first time 238 | checkAgentInfo(this.getXhrJson(0).event); 239 | 240 | // Respond to first request so that the 2nd request will be made 241 | this.requestList[0].respond(); 242 | 243 | expect(this.getXhrJson(1).event).toBe('hi'); 244 | 245 | LE.log('hi again'); 246 | this.requestList[1].respond(); 247 | 248 | // Check that no data is sent subsequent times 249 | expect(this.getXhrJson(2).event).toBe('hi again'); 250 | }); 251 | 252 | afterEach(destroy); 253 | }); 254 | 255 | describe('catch all option', function () { 256 | beforeEach(mockXMLHttpRequests); 257 | beforeEach(function () { 258 | this.oldErrorHandler = sinon.stub(GLOBAL, 'onerror') 259 | .returns(true); 260 | }); 261 | 262 | it('assigns onerror handler', function () { 263 | LE.init({ 264 | token: TOKEN, 265 | catchall: true 266 | }); 267 | // Don't test if onerror is set because #1 we've got a stub 268 | // and 2nd, karma has its handler. 269 | expect(GLOBAL.onerror).not.toBe(this.oldErrorHandler); 270 | }); 271 | 272 | it('sends errors', function () { 273 | // Don't care what happens to this, just ignore the error 274 | LE.init({ 275 | token: TOKEN, 276 | catchall: true 277 | }); 278 | 279 | // Check if onerror handler is not the stub from above 280 | expect(GLOBAL.onerror).not.toBe(this.oldErrorHandler); 281 | 282 | expect(this.requestList.length).toBe(0); 283 | 284 | // Pretend to trigger an error like the browser might 285 | GLOBAL.onerror('Script error', 'http://example.com', 0); 286 | 287 | expect(this.requestList.length).toBe(1); 288 | }); 289 | 290 | it('bubbles onerror calls', function () { 291 | LE.init({ 292 | token: TOKEN, 293 | catchall: true 294 | }); 295 | 296 | // Pretend to trigger an error like the browser might 297 | GLOBAL.onerror('Script error', 'http://example.com', 0); 298 | 299 | expect(this.oldErrorHandler.calledOnce).toBe(true); 300 | }); 301 | 302 | afterEach(function () { 303 | if (this.oldErrorHandler.restore) { 304 | this.oldErrorHandler.restore(); 305 | } 306 | }); 307 | afterEach(restoreXMLHttpRequests); 308 | afterEach(destroy); 309 | }); 310 | 311 | describe('destroys log streams', function () { 312 | it('default', function () { 313 | LE.init(TOKEN); 314 | LE.destroy(); 315 | 316 | expect(function () { 317 | LE.init(TOKEN); 318 | }).not.toThrow(); 319 | }); 320 | 321 | it('custom name', function () { 322 | LE.init({ 323 | token: TOKEN, 324 | name: 'test' 325 | }); 326 | LE.destroy('test'); 327 | 328 | expect(function () { 329 | LE.init({ 330 | token: TOKEN, 331 | name: 'test' 332 | }); 333 | }).not.toThrow(); 334 | LE.destroy('test'); 335 | }); 336 | 337 | afterEach(destroy); 338 | }); 339 | 340 | describe('no_format option', function () { 341 | beforeEach(mockXMLHttpRequests); 342 | beforeEach(addGetJson); 343 | 344 | it('Should send data to noformat if no format is enabled', function () { 345 | LE.init({ 346 | token: TOKEN, 347 | no_format: true 348 | }); 349 | LE.log('some message'); 350 | var url = this.requestList[0].url; 351 | expect(url).toContain("noformat"); 352 | }); 353 | 354 | it('Should send data to js if no format is disabled', function () { 355 | LE.init({ 356 | token: TOKEN, 357 | no_format: false 358 | }); 359 | LE.log('some message'); 360 | var url = this.requestList[0].url; 361 | expect(url).toContain("v1"); 362 | }); 363 | 364 | afterEach(restoreXMLHttpRequests); 365 | afterEach(destroy); 366 | }); 367 | 368 | 369 | describe('custom endpoint', function () { 370 | beforeEach(mockXMLHttpRequests); 371 | beforeEach(addGetJson); 372 | beforeEach(function () { 373 | window.LEENDPOINT = 'somewhere1.com/custom-logging'; 374 | LE.init({ 375 | token: TOKEN 376 | }); 377 | }); 378 | 379 | it('can be set', function () { 380 | LE.log('some message'); 381 | var lastReq = this.requestList[0]; 382 | 383 | expect(lastReq.url).toBe('https://somewhere1.com/custom-logging/logs/test_token'); 384 | }); 385 | 386 | afterEach(restoreXMLHttpRequests); 387 | afterEach(destroy); 388 | }); 389 | 390 | describe('print option', function () { 391 | beforeEach(mockXMLHttpRequests); 392 | beforeEach(function () { 393 | spyOn(console, 'log'); 394 | spyOn(console, 'info'); 395 | spyOn(console, 'warn'); 396 | spyOn(console, 'error'); 397 | LE.init({ 398 | token: TOKEN, 399 | print: true 400 | }); 401 | }); 402 | 403 | it('should log to console also', function () { 404 | LE.log('some message'); 405 | expect(console.log.mostRecentCall.args[0].trace).toMatch(/[0-9a-z]{8}/); 406 | expect(console.log.mostRecentCall.args[0].event).toEqual('some message'); 407 | expect(console.log.mostRecentCall.args[0].level).toEqual('LOG'); 408 | }); 409 | 410 | it('below IE9 should stringify console messages', function () { 411 | /*jshint -W020 */ 412 | XDomainRequest = XMLHttpRequest; //trick into thinking we are in IE8/9 browser 413 | /*jshint +W020 */ 414 | LE.log('some message'); 415 | expect(console.log.mostRecentCall.args[0]).toMatch(/[0-9a-z]{8} some message/); 416 | }); 417 | 418 | afterEach(restoreXMLHttpRequests); 419 | afterEach(destroy); 420 | }); 421 | -------------------------------------------------------------------------------- /product/le.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2015 Logentries. 3 | * Please view license at https://raw.github.com/logentries/le_js/master/LICENSE 4 | */ 5 | 6 | /*jslint browser:true*/ 7 | /*global define, module, exports, console, global */ 8 | 9 | /** @param {Object} window */ 10 | (function (root, factory) { 11 | if (typeof define === 'function' && define.amd) { 12 | // AMD. Register as an anonymous module. 13 | define(function() { 14 | return factory(root); 15 | }); 16 | } else if (typeof exports === 'object') { 17 | // Node. Does not work with strict CommonJS, but 18 | // only CommonJS-like environments that support module.exports, 19 | // like Node. 20 | if (typeof global === 'object') { 21 | // Browserify. The calling object `this` does not reference window. 22 | // `global` and `this` are equivalent in Node, preferring global 23 | // adds support for Browserify. 24 | root = global; 25 | } 26 | module.exports = factory(root); 27 | } else { 28 | // Browser globals (root is window) 29 | root.LE = factory(root); 30 | } 31 | }(this, function (window) { 32 | "use strict"; 33 | // cross-browser indexOf fix 34 | var _indexOf = function (array, obj) { 35 | for (var i = 0; i < array.length; i++) { 36 | if (obj === array[i]) { 37 | return i; 38 | } 39 | } 40 | return -1; 41 | }; 42 | 43 | // Obtain a browser-specific XHR object 44 | var _getAjaxObject = function () { 45 | if (typeof XDomainRequest !== "undefined") { 46 | // We're using IE8/9 47 | return new XDomainRequest(); 48 | } 49 | return new XMLHttpRequest(); 50 | }; 51 | 52 | /** 53 | * A single log event stream. 54 | * @constructor 55 | * @param {Object} options 56 | */ 57 | function LogStream(options) { 58 | /** 59 | * @const 60 | * @type {string} */ 61 | var _traceCode = options.trace ? (Math.random() + Math.PI).toString(36).substring(2, 10) : null; 62 | /** @type {string} */ 63 | var _pageInfo = options.page_info; 64 | /** @type {string} */ 65 | var _token = options.token; 66 | /** @type {boolean} */ 67 | var _print = options.print; 68 | /** @type {boolean} */ 69 | var _noFormat = options.no_format; 70 | /** @type {boolean} */ 71 | var _SSL = function() { 72 | if (typeof XDomainRequest === "undefined") { 73 | return options.ssl; 74 | } 75 | // If we're relying on XDomainRequest, we 76 | // must adhere to the page's encryption scheme. 77 | return window.location.protocol === "https:" ? true : false; 78 | }(); 79 | /** @type {string} */ 80 | var _endpoint; 81 | if (window.LEENDPOINT) { 82 | _endpoint = window.LEENDPOINT; 83 | } else if (_noFormat) { 84 | _endpoint = "webhook.logentries.com/noformat"; 85 | } 86 | else { 87 | _endpoint = "js.logentries.com/v1"; 88 | } 89 | _endpoint = (_SSL ? "https://" : "http://") + _endpoint + "/logs/" + _token; 90 | 91 | /** 92 | * Flag to prevent further invocations on network err 93 | ** @type {boolean} */ 94 | var _shouldCall = true; 95 | /** @type {Array.} */ 96 | var _backlog = []; 97 | /** @type {boolean} */ 98 | var _active = false; 99 | /** @type {boolean} */ 100 | var _sentPageInfo = false; 101 | 102 | if (options.catchall) { 103 | var oldHandler = window.onerror; 104 | var newHandler = function(msg, url, line) { 105 | _rawLog({error: msg, line: line, location: url}).level('ERROR').send(); 106 | if (oldHandler) { 107 | return oldHandler(msg, url, line); 108 | } else { 109 | return false; 110 | } 111 | }; 112 | window.onerror = newHandler; 113 | } 114 | 115 | var _agentInfo = function() { 116 | var nav = window.navigator || {doNotTrack: undefined}; 117 | var screen = window.screen || {}; 118 | var location = window.location || {}; 119 | 120 | return { 121 | url: location.pathname, 122 | referrer: document.referrer, 123 | screen: { 124 | width: screen.width, 125 | height: screen.height 126 | }, 127 | window: { 128 | width: window.innerWidth, 129 | height: window.innerHeight 130 | }, 131 | browser: { 132 | name: nav.appName, 133 | version: nav.appVersion, 134 | cookie_enabled: nav.cookieEnabled, 135 | do_not_track: nav.doNotTrack 136 | }, 137 | platform: nav.platform 138 | }; 139 | }; 140 | 141 | var _getEvent = function() { 142 | var raw = null; 143 | var args = Array.prototype.slice.call(arguments); 144 | if (args.length === 0) { 145 | throw new Error("No arguments!"); 146 | } else if (args.length === 1) { 147 | raw = args[0]; 148 | } else { 149 | // Handle a variadic overload, 150 | // e.g. _rawLog("some text ", x, " ...", 1); 151 | raw = args; 152 | } 153 | return raw; 154 | }; 155 | 156 | // Single arg stops the compiler arity warning 157 | var _rawLog = function(msg) { 158 | var event = _getEvent.apply(this, arguments); 159 | 160 | var data = {event: event}; 161 | 162 | // Add agent info if required 163 | if (_pageInfo !== 'never') { 164 | if (!_sentPageInfo || _pageInfo === 'per-entry') { 165 | _sentPageInfo = true; 166 | if (typeof event.screen === "undefined" && 167 | typeof event.browser === "undefined") 168 | _rawLog(_agentInfo()).level('PAGE').send(); 169 | } 170 | } 171 | 172 | if (_traceCode) { 173 | data.trace = _traceCode; 174 | } 175 | 176 | return {level: function(l) { 177 | // Don't log PAGE events to console 178 | // PAGE events are generated for the agentInfo function 179 | if (_print && typeof console !== "undefined" && l !== 'PAGE') { 180 | var serialized = null; 181 | if (typeof XDomainRequest !== "undefined") { 182 | // We're using IE8/9 183 | serialized = data.trace + ' ' + data.event; 184 | } 185 | try { 186 | console[l.toLowerCase()].call(console, (serialized || data)); 187 | } catch (ex) { 188 | // IE compat fix 189 | console.log((serialized || data)); 190 | } 191 | } 192 | data.level = l; 193 | 194 | return {send: function() { 195 | var cache = []; 196 | var serialized = JSON.stringify(data, function(key, value) { 197 | 198 | if (typeof value === "undefined") { 199 | return "undefined"; 200 | } else if (typeof value === "object" && value !== null) { 201 | if (_indexOf(cache, value) !== -1) { 202 | // We've seen this object before; 203 | // return a placeholder instead to prevent 204 | // cycles 205 | return ""; 206 | } 207 | cache.push(value); 208 | } 209 | return value; 210 | }); 211 | 212 | if (_active) { 213 | _backlog.push(serialized); 214 | } else { 215 | _apiCall(_token, serialized); 216 | } 217 | }}; 218 | }}; 219 | }; 220 | 221 | /** @expose */ 222 | this.log = _rawLog; 223 | 224 | var _apiCall = function(token, data) { 225 | _active = true; 226 | 227 | var request = _getAjaxObject(); 228 | 229 | if (_shouldCall) { 230 | if (request.constructor === XMLHttpRequest) { 231 | // Currently we don't support fine-grained error 232 | // handling in older versions of IE 233 | request.onreadystatechange = function() { 234 | if (request.readyState === 4) { 235 | // Handle any errors 236 | if (request.status >= 400) { 237 | console.error("Couldn't submit events."); 238 | if (request.status === 410) { 239 | // This API version has been phased out 240 | console.warn("This version of le_js is no longer supported!"); 241 | } 242 | } else { 243 | if (request.status === 301) { 244 | // Server issued a deprecation warning 245 | console.warn("This version of le_js is deprecated! Consider upgrading."); 246 | } 247 | if (_backlog.length > 0) { 248 | // Submit the next event in the backlog 249 | _apiCall(token, _backlog.shift()); 250 | } else { 251 | _active = false; 252 | } 253 | } 254 | } 255 | 256 | }; 257 | } else { 258 | request.onload = function() { 259 | if (_backlog.length > 0) { 260 | // Submit the next event in the backlog 261 | _apiCall(token, _backlog.shift()); 262 | } else { 263 | _active = false; 264 | } 265 | }; 266 | } 267 | 268 | request.open("POST", _endpoint, true); 269 | if (request.constructor === XMLHttpRequest) { 270 | request.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); 271 | request.setRequestHeader('Content-type', 'application/json'); 272 | } 273 | 274 | if (request.overrideMimeType) { 275 | request.overrideMimeType('text'); 276 | } 277 | 278 | request.send(data); 279 | } 280 | }; 281 | } 282 | 283 | /** 284 | * A single log object 285 | * @constructor 286 | * @param {Object} options 287 | */ 288 | function Logger(options) { 289 | var logger; 290 | 291 | // Default values 292 | var dict = { 293 | ssl: true, 294 | catchall: false, 295 | trace: true, 296 | page_info: 'never', 297 | print: false, 298 | endpoint: null, 299 | token: null 300 | }; 301 | 302 | if (typeof options === "object") 303 | for (var k in options) 304 | dict[k] = options[k]; 305 | else 306 | throw new Error("Invalid parameters for createLogStream()"); 307 | 308 | if (dict.token === null) { 309 | throw new Error("Token not present."); 310 | } else { 311 | logger = new LogStream(dict); 312 | } 313 | 314 | var _log = function(msg) { 315 | if (logger) { 316 | return logger.log.apply(this, arguments); 317 | } else 318 | throw new Error("You must call LE.init(...) first."); 319 | }; 320 | 321 | // The public interface 322 | return { 323 | log: function() { 324 | _log.apply(this, arguments).level('LOG').send(); 325 | }, 326 | warn: function() { 327 | _log.apply(this, arguments).level('WARN').send(); 328 | }, 329 | error: function() { 330 | _log.apply(this, arguments).level('ERROR').send(); 331 | }, 332 | info: function() { 333 | _log.apply(this, arguments).level('INFO').send(); 334 | } 335 | }; 336 | } 337 | 338 | // Array of Logger elements 339 | var loggers = {}; 340 | 341 | var _getLogger = function(name) { 342 | if (!loggers.hasOwnProperty(name)) 343 | throw new Error("Invalid name for logStream"); 344 | 345 | return loggers[name]; 346 | }; 347 | 348 | var _createLogStream = function(options) { 349 | if (typeof options.name !== "string") 350 | throw new Error("Name not present."); 351 | else if (loggers.hasOwnProperty(options.name)) 352 | throw new Error("A logger with that name already exists!"); 353 | loggers[options.name] = new Logger(options); 354 | 355 | return true; 356 | }; 357 | 358 | var _deprecatedInit = function(options) { 359 | var dict = { 360 | name : "default" 361 | }; 362 | 363 | if (typeof options === "object") 364 | for (var k in options) 365 | dict[k] = options[k]; 366 | else if (typeof options === "string") 367 | dict.token = options; 368 | else 369 | throw new Error("Invalid parameters for init()"); 370 | 371 | return _createLogStream(dict); 372 | }; 373 | 374 | var _destroyLogStream = function(name) { 375 | if (typeof name === 'undefined'){ 376 | name = 'default'; 377 | } 378 | 379 | delete loggers[name]; 380 | }; 381 | 382 | // The public interface 383 | return { 384 | init: _deprecatedInit, 385 | createLogStream: _createLogStream, 386 | to: _getLogger, 387 | destroy: _destroyLogStream, 388 | log: function() { 389 | for (var k in loggers) 390 | loggers[k].log.apply(this, arguments); 391 | }, 392 | warn: function() { 393 | for (var k in loggers) 394 | loggers[k].warn.apply(this, arguments); 395 | }, 396 | error: function() { 397 | for (var k in loggers) 398 | loggers[k].error.apply(this, arguments); 399 | }, 400 | info: function() { 401 | for (var k in loggers) 402 | loggers[k].info.apply(this, arguments); 403 | } 404 | }; 405 | })); 406 | -------------------------------------------------------------------------------- /src/le.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2015 Logentries. 3 | * Please view license at https://raw.github.com/logentries/le_js/master/LICENSE 4 | */ 5 | 6 | /*jslint browser:true*/ 7 | /*global define, module, exports, console, global */ 8 | 9 | /** @param {Object} window */ 10 | (function (root, factory) { 11 | if (typeof define === 'function' && define.amd) { 12 | // AMD. Register as an anonymous module. 13 | define(function() { 14 | return factory(root); 15 | }); 16 | } else if (typeof exports === 'object') { 17 | // Node. Does not work with strict CommonJS, but 18 | // only CommonJS-like environments that support module.exports, 19 | // like Node. 20 | if (typeof global === 'object') { 21 | // Browserify. The calling object `this` does not reference window. 22 | // `global` and `this` are equivalent in Node, preferring global 23 | // adds support for Browserify. 24 | root = global; 25 | } 26 | module.exports = factory(root); 27 | } else { 28 | // Browser globals (root is window) 29 | root.LE = factory(root); 30 | } 31 | }(this, function (window) { 32 | "use strict"; 33 | // cross-browser indexOf fix 34 | var _indexOf = function (array, obj) { 35 | for (var i = 0; i < array.length; i++) { 36 | if (obj === array[i]) { 37 | return i; 38 | } 39 | } 40 | return -1; 41 | }; 42 | 43 | // Obtain a browser-specific XHR object 44 | var _getAjaxObject = function () { 45 | if (typeof XDomainRequest !== "undefined") { 46 | // We're using IE8/9 47 | return new XDomainRequest(); 48 | } 49 | return new XMLHttpRequest(); 50 | }; 51 | 52 | /** 53 | * A single log event stream. 54 | * @constructor 55 | * @param {Object} options 56 | */ 57 | function LogStream(options) { 58 | /** 59 | * @const 60 | * @type {string} */ 61 | var _traceCode = options.trace ? (Math.random() + Math.PI).toString(36).substring(2, 10) : null; 62 | /** @type {string} */ 63 | var _pageInfo = options.page_info; 64 | /** @type {string} */ 65 | var _token = options.token; 66 | /** @type {boolean} */ 67 | var _print = options.print; 68 | /** @type {boolean} */ 69 | var _noFormat = options.no_format; 70 | /** @type {boolean} */ 71 | var _SSL = function() { 72 | if (typeof XDomainRequest === "undefined") { 73 | return options.ssl; 74 | } 75 | // If we're relying on XDomainRequest, we 76 | // must adhere to the page's encryption scheme. 77 | return window.location.protocol === "https:" ? true : false; 78 | }(); 79 | /** @type {string} */ 80 | var _endpoint; 81 | if (window.LEENDPOINT) { 82 | _endpoint = window.LEENDPOINT; 83 | } else if (_noFormat) { 84 | _endpoint = "localhost:8080/noformat"; 85 | } 86 | else { 87 | _endpoint = "localhost:8080/v1"; 88 | } 89 | _endpoint = (_SSL ? "https://" : "http://") + _endpoint + "/logs/" + _token; 90 | 91 | /** 92 | * Flag to prevent further invocations on network err 93 | ** @type {boolean} */ 94 | var _shouldCall = true; 95 | /** @type {Array.} */ 96 | var _backlog = []; 97 | /** @type {boolean} */ 98 | var _active = false; 99 | /** @type {boolean} */ 100 | var _sentPageInfo = false; 101 | 102 | if (options.catchall) { 103 | var oldHandler = window.onerror; 104 | var newHandler = function(msg, url, line) { 105 | _rawLog({error: msg, line: line, location: url}).level('ERROR').send(); 106 | if (oldHandler) { 107 | return oldHandler(msg, url, line); 108 | } else { 109 | return false; 110 | } 111 | }; 112 | window.onerror = newHandler; 113 | } 114 | 115 | var _agentInfo = function() { 116 | var nav = window.navigator || {doNotTrack: undefined}; 117 | var screen = window.screen || {}; 118 | var location = window.location || {}; 119 | 120 | return { 121 | url: location.pathname, 122 | referrer: document.referrer, 123 | screen: { 124 | width: screen.width, 125 | height: screen.height 126 | }, 127 | window: { 128 | width: window.innerWidth, 129 | height: window.innerHeight 130 | }, 131 | browser: { 132 | name: nav.appName, 133 | user_agent: nav.userAgent, 134 | version: nav.appVersion, 135 | cookie_enabled: nav.cookieEnabled, 136 | do_not_track: nav.doNotTrack 137 | }, 138 | platform: nav.platform 139 | }; 140 | }; 141 | 142 | var _getEvent = function() { 143 | var raw = null; 144 | var args = Array.prototype.slice.call(arguments); 145 | if (args.length === 0) { 146 | throw new Error("No arguments!"); 147 | } else if (args.length === 1) { 148 | raw = args[0]; 149 | } else { 150 | // Handle a variadic overload, 151 | // e.g. _rawLog("some text ", x, " ...", 1); 152 | raw = args; 153 | } 154 | return raw; 155 | }; 156 | 157 | // Single arg stops the compiler arity warning 158 | var _rawLog = function(msg) { 159 | var event = _getEvent.apply(this, arguments); 160 | 161 | var data = {event: event}; 162 | 163 | // Add agent info if required 164 | if (_pageInfo !== 'never') { 165 | if (!_sentPageInfo || _pageInfo === 'per-entry') { 166 | _sentPageInfo = true; 167 | if (typeof event.screen === "undefined" && 168 | typeof event.browser === "undefined") 169 | _rawLog(_agentInfo()).level('PAGE').send(); 170 | } 171 | } 172 | 173 | if (_traceCode) { 174 | data.trace = _traceCode; 175 | } 176 | 177 | return {level: function(l) { 178 | // Don't log PAGE events to console 179 | // PAGE events are generated for the agentInfo function 180 | if (_print && typeof console !== "undefined" && l !== 'PAGE') { 181 | var serialized = null; 182 | if (typeof XDomainRequest !== "undefined") { 183 | // We're using IE8/9 184 | serialized = data.trace + ' ' + data.event; 185 | } 186 | try { 187 | console[l.toLowerCase()].call(console, (serialized || data)); 188 | } catch (ex) { 189 | // IE compat fix 190 | console.log((serialized || data)); 191 | } 192 | } 193 | data.level = l; 194 | 195 | return {send: function() { 196 | var cache = []; 197 | var serialized = JSON.stringify(data, function(key, value) { 198 | 199 | if (typeof value === "undefined") { 200 | return "undefined"; 201 | } else if (typeof value === "object" && value !== null) { 202 | if (_indexOf(cache, value) !== -1) { 203 | // We've seen this object before; 204 | // return a placeholder instead to prevent 205 | // cycles 206 | return ""; 207 | } 208 | cache.push(value); 209 | } 210 | return value; 211 | }); 212 | 213 | if (_active) { 214 | _backlog.push(serialized); 215 | } else { 216 | _apiCall(_token, serialized); 217 | } 218 | }}; 219 | }}; 220 | }; 221 | 222 | /** @expose */ 223 | this.log = _rawLog; 224 | 225 | var _apiCall = function(token, data) { 226 | _active = true; 227 | 228 | var request = _getAjaxObject(); 229 | 230 | if (_shouldCall) { 231 | if (request.constructor === XMLHttpRequest) { 232 | // Currently we don't support fine-grained error 233 | // handling in older versions of IE 234 | request.onreadystatechange = function() { 235 | if (request.readyState === 4) { 236 | // Handle any errors 237 | if (request.status >= 400) { 238 | console.error("Couldn't submit events."); 239 | if (request.status === 410) { 240 | // This API version has been phased out 241 | console.warn("This version of le_js is no longer supported!"); 242 | } 243 | } else { 244 | if (request.status === 301) { 245 | // Server issued a deprecation warning 246 | console.warn("This version of le_js is deprecated! Consider upgrading."); 247 | } 248 | if (_backlog.length > 0) { 249 | // Submit the next event in the backlog 250 | _apiCall(token, _backlog.shift()); 251 | } else { 252 | _active = false; 253 | } 254 | } 255 | } 256 | 257 | }; 258 | } else { 259 | request.onload = function() { 260 | if (_backlog.length > 0) { 261 | // Submit the next event in the backlog 262 | _apiCall(token, _backlog.shift()); 263 | } else { 264 | _active = false; 265 | } 266 | }; 267 | } 268 | 269 | request.open("POST", _endpoint, true); 270 | if (request.constructor === XMLHttpRequest) { 271 | request.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); 272 | request.setRequestHeader('Content-type', 'application/json'); 273 | } 274 | 275 | if (request.overrideMimeType) { 276 | request.overrideMimeType('text'); 277 | } 278 | 279 | request.send(data); 280 | } 281 | }; 282 | } 283 | 284 | /** 285 | * A single log object 286 | * @constructor 287 | * @param {Object} options 288 | */ 289 | function Logger(options) { 290 | var logger; 291 | 292 | // Default values 293 | var dict = { 294 | ssl: true, 295 | catchall: false, 296 | trace: true, 297 | page_info: 'never', 298 | print: false, 299 | endpoint: null, 300 | token: null 301 | }; 302 | 303 | if (typeof options === "object") 304 | for (var k in options) 305 | dict[k] = options[k]; 306 | else 307 | throw new Error("Invalid parameters for createLogStream()"); 308 | 309 | if (dict.token === null) { 310 | throw new Error("Token not present."); 311 | } else { 312 | logger = new LogStream(dict); 313 | } 314 | 315 | var _log = function(msg) { 316 | if (logger) { 317 | return logger.log.apply(this, arguments); 318 | } else 319 | throw new Error("You must call LE.init(...) first."); 320 | }; 321 | 322 | // The public interface 323 | return { 324 | log: function() { 325 | _log.apply(this, arguments).level('LOG').send(); 326 | }, 327 | warn: function() { 328 | _log.apply(this, arguments).level('WARN').send(); 329 | }, 330 | error: function() { 331 | _log.apply(this, arguments).level('ERROR').send(); 332 | }, 333 | info: function() { 334 | _log.apply(this, arguments).level('INFO').send(); 335 | } 336 | }; 337 | } 338 | 339 | // Array of Logger elements 340 | var loggers = {}; 341 | 342 | var _getLogger = function(name) { 343 | if (!loggers.hasOwnProperty(name)) 344 | throw new Error("Invalid name for logStream"); 345 | 346 | return loggers[name]; 347 | }; 348 | 349 | var _createLogStream = function(options) { 350 | if (typeof options.name !== "string") 351 | throw new Error("Name not present."); 352 | else if (loggers.hasOwnProperty(options.name)) 353 | throw new Error("A logger with that name already exists!"); 354 | loggers[options.name] = new Logger(options); 355 | 356 | return true; 357 | }; 358 | 359 | var _deprecatedInit = function(options) { 360 | var dict = { 361 | name : "default" 362 | }; 363 | 364 | if (typeof options === "object") 365 | for (var k in options) 366 | dict[k] = options[k]; 367 | else if (typeof options === "string") 368 | dict.token = options; 369 | else 370 | throw new Error("Invalid parameters for init()"); 371 | 372 | return _createLogStream(dict); 373 | }; 374 | 375 | var _destroyLogStream = function(name) { 376 | if (typeof name === 'undefined'){ 377 | name = 'default'; 378 | } 379 | 380 | delete loggers[name]; 381 | }; 382 | 383 | // The public interface 384 | return { 385 | init: _deprecatedInit, 386 | createLogStream: _createLogStream, 387 | to: _getLogger, 388 | destroy: _destroyLogStream, 389 | log: function() { 390 | for (var k in loggers) 391 | loggers[k].log.apply(this, arguments); 392 | }, 393 | warn: function() { 394 | for (var k in loggers) 395 | loggers[k].warn.apply(this, arguments); 396 | }, 397 | error: function() { 398 | for (var k in loggers) 399 | loggers[k].error.apply(this, arguments); 400 | }, 401 | info: function() { 402 | for (var k in loggers) 403 | loggers[k].info.apply(this, arguments); 404 | } 405 | }; 406 | })); 407 | -------------------------------------------------------------------------------- /product/le.dyn.js: -------------------------------------------------------------------------------- 1 | var XMLHttpRequest = function() { 2 | 3 | var _channel = java.nio.channels.AsynchronousSocketChannel.open(); 4 | var _method = null; 5 | var _address = null; 6 | var _path = null; 7 | var _headers = {"User-Agent": "dyn.xhr/0.0.1"}; 8 | var _encoder = java.nio.charset.Charset.forName("US-ASCII").newEncoder(); 9 | 10 | _encoder.reset(); 11 | 12 | this.readyState = 0; 13 | 14 | this.onreadystatechange = function() {}; 15 | 16 | this.open = function(method, url, async) { 17 | _method = method; 18 | var uri = new java.net.URI(url); 19 | var port = (uri.getPort() === -1 ? 80 : uri.getPort()); 20 | _address = new java.net.InetSocketAddress(uri.getHost(), port); 21 | _path = uri.getPath(); 22 | } 23 | 24 | this.setRequestHeader = function(key, value) { 25 | _headers[key] = value; 26 | } 27 | 28 | var _buildRequest = function(data) { 29 | var request = _method + " " + _path + " HTTP/1.1\r\n"; 30 | _headers["Content-Length"] = data.length; 31 | _headers["Accept"] = "*/*"; 32 | _headers["Host"] = "localhost:8080"; 33 | for (var k in _headers) { 34 | request += k + ": " + _headers[k] + "\r\n"; 35 | } 36 | request += ("\r\n" + data); 37 | return request; 38 | } 39 | 40 | var _parseResponse = function(data) { 41 | var resp = data.split("\r\n"); 42 | that.status = resp[0].split(" ")[1]; 43 | } 44 | 45 | this.status = 0; 46 | 47 | var that = this; 48 | 49 | this.send = function(data) { 50 | var req = _buildRequest(data); 51 | var cb = java.nio.CharBuffer.wrap(req); 52 | var bb = _encoder.encode(cb); 53 | _channel.connect(_address, null, new java.nio.channels.CompletionHandler({ 54 | completed: function(result, attachment) { 55 | that.readyState = 1; 56 | _channel.write(bb, null, new java.nio.channels.CompletionHandler({ 57 | completed: function(result, attachment) { 58 | var bb = java.nio.ByteBuffer.allocate(4096); 59 | _channel.read(bb, null, new java.nio.channels.CompletionHandler({ 60 | completed: function(result, attachment) { 61 | var str = new java.lang.String(bb.array()); 62 | var resp = _parseResponse(str); 63 | _channel.close(); 64 | that.readyState = 4; 65 | that.onreadystatechange(); 66 | } 67 | })); 68 | }, 69 | failed: function(err, attachment) { 70 | print(err); 71 | _channel.close(); 72 | } 73 | })); 74 | }, 75 | failed: function(err, attachment) { 76 | print(err); 77 | _channel.close(); 78 | } 79 | })); 80 | } 81 | } 82 | 83 | /** 84 | * @license Copyright 2015 Logentries. 85 | * Please view license at https://raw.github.com/logentries/le_js/master/LICENSE 86 | */ 87 | 88 | /*global define, module, exports */ 89 | 90 | /** @param {Object} window */ 91 | (function (root, factory) { 92 | if (typeof define === 'function' && define.amd) { 93 | // AMD. Register as an anonymous module. 94 | define([root], factory); 95 | } else if (typeof exports === 'object') { 96 | // Node. Does not work with strict CommonJS, but 97 | // only CommonJS-like environments that support module.exports, 98 | // like Node. 99 | module.exports = factory(root); 100 | } else { 101 | // Browser globals (root is window) 102 | root.LE = factory(root); 103 | } 104 | }(this, function(window) { 105 | "use strict"; 106 | 107 | /** 108 | * A single log event stream. 109 | * @constructor 110 | * @param {Object} options 111 | */ 112 | function LogStream(options) { 113 | /** 114 | * @const 115 | * @type {string} */ 116 | var _traceCode = (Math.random() + Math.PI).toString(36).substring(2, 10); 117 | /** @type {boolean} */ 118 | var _doTrace = options.trace; 119 | /** @type {string} */ 120 | var _pageInfo = options.page_info; 121 | /** @type {string} */ 122 | var _token = options.token; 123 | /** @type {boolean} */ 124 | var _print = options.print; 125 | /** 126 | * @const 127 | * @type {string} */ 128 | var _endpoint = "js.logentries.com/v1"; 129 | 130 | /** 131 | * Flag to prevent further invocations on network err 132 | ** @type {boolean} */ 133 | var _shouldCall = true; 134 | /** @type {boolean} */ 135 | var _SSL = function() { 136 | if (typeof XDomainRequest === "undefined") { 137 | return options.ssl; 138 | } 139 | // If we're relying on XDomainRequest, we 140 | // must adhere to the page's encryption scheme. 141 | return window.location.protocol === "https:" ? true : false; 142 | }(); 143 | /** @type {Array.} */ 144 | var _backlog = []; 145 | /** @type {boolean} */ 146 | var _active = false; 147 | /** @type {boolean} */ 148 | var _sentPageInfo = false; 149 | 150 | if (options.catchall) { 151 | var oldHandler = window.onerror; 152 | var newHandler = function(msg, url, line) { 153 | _rawLog({error: msg, line: line, location: url}).level('ERROR').send(); 154 | if (oldHandler) { 155 | return oldHandler(msg, url, line); 156 | } else { 157 | return false; 158 | } 159 | }; 160 | window.onerror = newHandler; 161 | } 162 | 163 | var _agentInfo = function() { 164 | var nav = window.navigator || {doNotTrack: undefined}; 165 | var screen = window.screen || {}; 166 | var location = window.location || {}; 167 | 168 | return { 169 | url: location.pathname, 170 | referrer: document.referrer, 171 | screen: { 172 | width: screen.width, 173 | height: screen.height 174 | }, 175 | window: { 176 | width: window.innerWidth, 177 | height: window.innerHeight 178 | }, 179 | browser: { 180 | name: nav.appName, 181 | version: nav.appVersion, 182 | cookie_enabled: nav.cookieEnabled, 183 | do_not_track: nav.doNotTrack 184 | }, 185 | platform: nav.platform 186 | } 187 | }; 188 | 189 | var _getEvent = function() { 190 | var raw = null; 191 | var args = Array.prototype.slice.call(arguments); 192 | if (args.length === 0) { 193 | throw new Error("No arguments!"); 194 | } else if (args.length === 1) { 195 | raw = args[0]; 196 | } else { 197 | // Handle a variadic overload, 198 | // e.g. _rawLog("some text ", x, " ...", 1); 199 | raw = args; 200 | } 201 | return raw; 202 | }; 203 | 204 | // Single arg stops the compiler arity warning 205 | var _rawLog = function(msg) { 206 | var event = _getEvent.apply(this, arguments); 207 | 208 | var data = {event: event}; 209 | 210 | // Add agent info if required 211 | if (_pageInfo !== 'never') { 212 | if (!_sentPageInfo || _pageInfo === 'per-entry') { 213 | _sentPageInfo = true; 214 | if (typeof event.screen === "undefined" && 215 | typeof event.browser === "undefined") 216 | _rawLog(_agentInfo()).level('PAGE').send(); 217 | } 218 | } 219 | 220 | // Add trace code if required 221 | if (_doTrace) { 222 | data.trace = _traceCode; 223 | } 224 | 225 | return {level: function(l) { 226 | if (_print && typeof console !== "undefined" && l !== 'PAGE') { 227 | try { 228 | console[l.toLowerCase()].call(console, data); 229 | } catch (ex) { 230 | // IE compat fix 231 | console.log(data); 232 | } 233 | } 234 | data.level = l; 235 | 236 | return {send: function() { 237 | var cache = []; 238 | var serialized = JSON.stringify(data, function(key, value) { 239 | 240 | // cross-browser indexOf fix 241 | var _indexOf = function(array, obj) { 242 | for (var i = 0; i < array.length; i++) { 243 | if (obj === array[i]) { 244 | return i; 245 | } 246 | } 247 | return -1; 248 | } 249 | if (typeof value === "undefined") { 250 | return "undefined"; 251 | } else if (typeof value === "object" && value !== null) { 252 | if (_indexOf(cache, value) !== -1) { 253 | // We've seen this object before; 254 | // return a placeholder instead to prevent 255 | // cycles 256 | return ""; 257 | } 258 | cache.push(value); 259 | } 260 | return value; 261 | }); 262 | 263 | if (_active) { 264 | _backlog.push(serialized); 265 | } else { 266 | _apiCall(_token, serialized); 267 | } 268 | }}; 269 | }}; 270 | }; 271 | 272 | /** @expose */ 273 | this.log = _rawLog; 274 | 275 | var _apiCall = function(token, data) { 276 | _active = true; 277 | 278 | // Obtain a browser-specific XHR object 279 | var _getAjaxObject = function() { 280 | if (typeof XDomainRequest !== "undefined") { 281 | // We're using IE8/9 282 | return new XDomainRequest(); 283 | } 284 | return new XMLHttpRequest(); 285 | }; 286 | 287 | var request = _getAjaxObject(); 288 | 289 | if (_shouldCall) { 290 | if (request.constructor === XMLHttpRequest) { 291 | // Currently we don't support fine-grained error 292 | // handling in older versions of IE 293 | request.onreadystatechange = function() { 294 | if (request.readyState === 4) { 295 | // Handle any errors 296 | if (request.status >= 400) { 297 | console.error("Couldn't submit events."); 298 | if (request.status === 410) { 299 | // This API version has been phased out 300 | console.warn("This version of le_js is no longer supported!"); 301 | } 302 | } else { 303 | if (request.status === 301) { 304 | // Server issued a deprecation warning 305 | console.warn("This version of le_js is deprecated! Consider upgrading."); 306 | } 307 | if (_backlog.length > 0) { 308 | // Submit the next event in the backlog 309 | _apiCall(token, _backlog.shift()); 310 | } else { 311 | _active = false; 312 | } 313 | } 314 | } 315 | 316 | } 317 | } else { 318 | request.onload = function() { 319 | if (_backlog.length > 0) { 320 | // Submit the next event in the backlog 321 | _apiCall(token, _backlog.shift()); 322 | } else { 323 | _active = false; 324 | } 325 | } 326 | } 327 | 328 | var uri = (_SSL ? "https://" : "http://") + _endpoint + "/logs/" + _token; 329 | request.open("POST", uri, true); 330 | if (request.constructor === XMLHttpRequest) { 331 | request.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); 332 | request.setRequestHeader('Content-type', 'text/json'); 333 | } 334 | request.send(data); 335 | } 336 | }; 337 | } 338 | 339 | /** 340 | * A single log object 341 | * @constructor 342 | * @param {Object} options 343 | */ 344 | function Logger(options) { 345 | var logger; 346 | 347 | // Default values 348 | var dict = { 349 | ssl: true, 350 | catchall: false, 351 | trace: true, 352 | page_info: 'never', 353 | print: false, 354 | token: null 355 | }; 356 | 357 | if (typeof options === "object") 358 | for (var k in options) 359 | dict[k] = options[k]; 360 | else 361 | throw new Error("Invalid parameters for createLogStream()"); 362 | 363 | if (dict.token === null) { 364 | throw new Error("Token not present."); 365 | } else { 366 | logger = new LogStream(dict); 367 | } 368 | 369 | var _log = function(msg) { 370 | if (logger) { 371 | return logger.log.apply(this, arguments); 372 | } else 373 | throw new Error("You must call LE.init(...) first."); 374 | }; 375 | 376 | // The public interface 377 | return { 378 | log: function() { 379 | _log.apply(this, arguments).level('LOG').send(); 380 | }, 381 | warn: function() { 382 | _log.apply(this, arguments).level('WARN').send(); 383 | }, 384 | error: function() { 385 | _log.apply(this, arguments).level('ERROR').send(); 386 | }, 387 | info: function() { 388 | _log.apply(this, arguments).level('INFO').send(); 389 | } 390 | }; 391 | } 392 | 393 | // Array of Logger elements 394 | var loggers = {}; 395 | 396 | var _getLogger = function(name) { 397 | if (!loggers.hasOwnProperty(name)) 398 | throw new Error("Invalid name for logStream"); 399 | 400 | return loggers[name] 401 | } 402 | 403 | var _createLogStream = function(options) { 404 | if (typeof options.name !== "string") 405 | throw new Error("Name not present."); 406 | else if (loggers.hasOwnProperty(options.name)) 407 | throw new Error("Alrready exist this name for a logStream"); 408 | 409 | loggers[options.name] = new Logger(options); 410 | 411 | return true; 412 | }; 413 | 414 | var _deprecatedInit = function(options) { 415 | var dict = { 416 | name : "default" 417 | }; 418 | 419 | if (typeof options === "object") 420 | for (var k in options) 421 | dict[k] = options[k]; 422 | else if (typeof options === "string") 423 | dict.token = options; 424 | else 425 | throw new Error("Invalid parameters for init()"); 426 | 427 | return _createLogStream(dict); 428 | } 429 | 430 | // The public interface 431 | return { 432 | init: _deprecatedInit, 433 | createLogStream: _createLogStream, 434 | to: _getLogger, 435 | log: function() { 436 | for (var k in loggers) 437 | loggers[k].log.apply(this, arguments); 438 | }, 439 | warn: function() { 440 | for (var k in loggers) 441 | loggers[k].warn.apply(this, arguments); 442 | }, 443 | error: function() { 444 | for (var k in loggers) 445 | loggers[k].error.apply(this, arguments); 446 | }, 447 | info: function() { 448 | for (var k in loggers) 449 | loggers[k].info.apply(this, arguments); 450 | } 451 | }; 452 | })); 453 | --------------------------------------------------------------------------------