├── index.js
├── .gitignore
├── .editorconfig
├── CONTRIBUTING.md
├── .travis.yml
├── bower.json
├── LICENSE.md
├── demo
├── index.html
└── app.js
├── Gruntfile.js
├── karma.conf.js
├── angular-loggly-logger.min.js
├── angular-loggly-logger.min.map
├── angular-loggly-logger.min.js.map
├── package.json
├── .jshintrc
├── README.md
├── angular-loggly-logger.js
└── test
└── unit
└── logglySenderSpec.js
/index.js:
--------------------------------------------------------------------------------
1 | require('angular');
2 | require('./angular-loggly-logger');
3 | module.exports = 'logglyLogger';
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | logs/*
2 | !.gitkeep
3 | build/
4 | node_modules/
5 | bower_components/
6 | tmp
7 | .DS_Store
8 | .idea
9 | *.iml
10 |
11 | *.swp
12 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
12 | [*.md]
13 | trim_trailing_whitespace = false
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | Contributing
2 | ------------
3 |
4 | Contributions are awesome, welcomed, and wanted. Please make sure your pull request targets the "develop" branch. Don't forget to include new or updated tests!
5 |
6 | Please commit the re-minified versions of files after making your changes. Minification can be done using `grunt`. Always fix any jsHint issues that are reported.
7 |
8 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "node"
4 | deploy:
5 | - provider: npm
6 | email: aj@ajbrown.org
7 | api_key:
8 | secure: C07sCtkimrvDgQZZUVWjlPD/axfRAYJEWMYW6Cu5T1qSz1BZwKmXxNLJouKk1Q+jwlWVWNnJ0kOL/cUTN7oo1YaIsHbE7ufxJoHeyPnOu7oz0FRxYDplZ/3ukQteDHxZkIYjgzfgXWGXbEDoOfzT63+DsDMXQDn7CQrs6bXxv0E=
9 | on:
10 | branch: master
11 | tags: true
12 |
13 | before_script:
14 | - export DISPLAY=:99.0
15 | - sh -e /etc/init.d/xvfb start
16 | - npm install -g grunt-cli
17 | - npm start > /dev/null &
18 | - npm run update-webdriver
19 | - sleep 1
20 | script:
21 | - grunt test
22 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-loggly-logger",
3 | "version": "0.3.2",
4 | "main": "angular-loggly-logger.js",
5 | "keywords": [
6 | "angularjs",
7 | "loggly",
8 | "logging",
9 | "logger",
10 | "log"
11 | ],
12 | "authors": [
13 | {
14 | "name": "A.J. Brown",
15 | "email": "aj@ajbrown.org"
16 | }
17 | ],
18 | "ignore": [
19 | "demo",
20 | "test",
21 | "Gruntfile.js",
22 | "package.json",
23 | "karma.conf.js",
24 | "karma-e2e.conf.js",
25 | "node_modules",
26 | "bower_components"
27 | ],
28 | "dependencies": {
29 | "angular": "^1.5.6"
30 | },
31 | "devDependencies": {
32 | "angular-mocks": "^1.5.6",
33 | "angular-loader": "^1.5.6"
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright 2017 [A.J. Brown](https://ajbrown.org)
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
8 |
--------------------------------------------------------------------------------
/demo/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Make sure you edit demo/app.js, and add a valid inputToken to the "logglyInputToken" constant's value.!
10 |
11 |
12 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/demo/app.js:
--------------------------------------------------------------------------------
1 | ; (function(angular) {
2 |
3 | angular.module( 'logglyLogger.demo', ['logglyLogger'] )
4 |
5 | .constant(
6 | 'logglyInputToken', ''
7 | )
8 |
9 | .config( function( LogglyLoggerProvider, logglyInputToken ) {
10 |
11 | LogglyLoggerProvider
12 | .inputToken( logglyInputToken )
13 | .useHttps( true )
14 | .includeTimestamp( true )
15 | .includeUrl( true )
16 | .sendConsoleErrors( true )
17 | .logToConsole( true )
18 | ;
19 |
20 | } )
21 |
22 | .controller( 'MainCtrl', function( $scope, $log, LogglyLogger, logglyInputToken ) {
23 |
24 | $scope.inputToken = logglyInputToken;
25 | $scope.message = '';
26 | $scope.extra = '{}';
27 |
28 | //We can also create named loggers, similar to log4j
29 | var megaLog = $log.getLogger( 'MegaLogger' );
30 |
31 | $scope.updateExtra = function() {
32 | LogglyLogger.fields( angular.fromJson( $scope.extra ) );
33 | $log.info( "Updated fields:", LogglyLogger.fields() );
34 | };
35 |
36 | $scope.logIt = function() {
37 | $log.info( $scope.message );
38 | };
39 |
40 | $scope.megaLogIt = function() {
41 | megaLog.warn( $scope.message );
42 | };
43 |
44 | })
45 |
46 |
47 | ;
48 |
49 |
50 | })(window.angular);
51 |
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 | module.exports = function(grunt) {
2 | grunt.initConfig({
3 | pkg: grunt.file.readJSON('package.json'),
4 |
5 | jshint: {
6 | options: {
7 | jshintrc: true
8 | },
9 | files: {
10 | src: ['angular-loggly-logger.js']
11 | }
12 | },
13 |
14 | uglify: {
15 | options : {
16 | sourceMap: true
17 | },
18 | main: {
19 | files: { 'angular-loggly-logger.min.js': ['angular-loggly-logger.js'] }
20 | }
21 | },
22 |
23 | karma: {
24 | unit: {
25 | configFile: 'karma.conf.js'
26 | },
27 | //continuous integration mode: run tests once in PhantomJS browser.
28 | travis: {
29 | configFile: 'karma.conf.js',
30 | singleRun: true,
31 | browsers: ['Firefox']
32 | }
33 | },
34 |
35 | watch: {
36 |
37 | //run JSHint on JS files
38 | jshint: {
39 | files: ['angular-loggly-logger.js'],
40 | tasks: ['jshint']
41 | },
42 |
43 | //run unit tests with karma (server needs to be already running)
44 | karma: {
45 | files: ['*.js', '!*.min.js'],
46 | tasks: ['karma:unit:run'] //NOTE the :run flag
47 | }
48 | }
49 |
50 | });
51 |
52 | grunt.loadNpmTasks('grunt-contrib-jshint');
53 | grunt.loadNpmTasks('grunt-contrib-uglify');
54 | grunt.loadNpmTasks('grunt-contrib-watch');
55 | grunt.loadNpmTasks('grunt-karma');
56 |
57 | grunt.registerTask('default', ['jshint','uglify', 'test-all'] );
58 | grunt.registerTask('test', [ 'karma:travis' ] );
59 | grunt.registerTask('test-all', ['karma:unit'] );
60 | };
61 |
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | module.exports = function(config){
2 | config.set({
3 |
4 | files : [
5 | 'node_modules/angular/angular.js',
6 | 'node_modules/angular-mocks/angular-mocks.js',
7 | 'angular-loggly-logger.js',
8 | 'test/unit/**/*.js'
9 | ],
10 |
11 | autoWatch : true,
12 |
13 | frameworks: ['jasmine'],
14 |
15 | browsers : ['Chrome','Firefox'],
16 |
17 | reporters: ['spec','coverage', 'coveralls'],
18 |
19 | preprocessors: {
20 | // source files, that you wanna generate coverage for
21 | // do not include tests or libraries
22 | // (these files will be instrumented by Istanbul)
23 | '*.js': ['coverage']
24 | },
25 |
26 | plugins : [
27 | 'karma-chrome-launcher',
28 | 'karma-firefox-launcher',
29 | 'karma-phantomjs-launcher',
30 | 'karma-spec-reporter',
31 | 'karma-jasmine',
32 | 'karma-coverage',
33 | 'karma-coveralls'
34 | ],
35 |
36 | junitReporter : {
37 | outputFile: 'build/reports/test-results/unit.xml',
38 | suite: 'unit'
39 | },
40 |
41 | coverageReporter: {
42 | dir : 'build/reports/coverage/',
43 | reporters: [
44 | // reporters not supporting the `file` property
45 | { type: 'html', subdir: 'report-html' },
46 | { type: 'lcov', subdir: 'report-lcov' },
47 | // reporters supporting the `file` property, use `subdir` to directly
48 | // output them in the `dir` directory
49 | { type: 'cobertura', subdir: '.', file: 'cobertura.txt' },
50 | { type: 'lcovonly', subdir: '.', file: 'report-lcovonly.txt' },
51 | { type: 'teamcity', subdir: '.', file: 'teamcity.txt' },
52 | { type: 'text', subdir: '.', file: 'text.txt' },
53 | { type: 'text-summary', subdir: '.', file: 'text-summary.txt' },
54 | ]
55 | }
56 | });
57 | };
58 |
--------------------------------------------------------------------------------
/angular-loggly-logger.min.js:
--------------------------------------------------------------------------------
1 | !function(a){"use strict";a.module("logglyLogger.logger",[]).provider("LogglyLogger",function(){var b=this,c=["DEBUG","INFO","WARN","ERROR"],d=!0,e={},f=!1,g=!1,h=!1,i=null,j=!1,k=!0,l=!0,m={},n=!1,o=0,p=null,q=function(){return(d?"https":"http")+"://logs-01.loggly.com/inputs/"+p+"/tag/"+(i||"AngularJS")+"/"};this.setExtra=function(a){return e=a,b},this.fields=function(c){return a.isDefined(c)?(a.extend(e,c),b):e},this.labels=function(c){return a.isObject(c)?(m=c,b):m},this.inputToken=function(c){return a.isDefined(c)?(p=c,b):p},this.useHttps=function(c){return a.isDefined(c)?(d=!!c,b):d},this.includeUrl=function(c){return a.isDefined(c)?(f=!!c,b):f},this.includeTimestamp=function(c){return a.isDefined(c)?(g=!!c,b):g},this.includeUserAgent=function(c){return a.isDefined(c)?(h=!!c,b):h},this.inputTag=function(c){return a.isDefined(c)?(i=c,b):i},this.sendConsoleErrors=function(c){return a.isDefined(c)?(j=!!c,b):j},this.level=function(d){if(a.isDefined(d)){var e=c.indexOf(d.toUpperCase());if(e<0)throw"Invalid logging level specified: "+d;return o=e,b}return c[o]},this.isLevelEnabled=function(a){return c.indexOf(a.toUpperCase())>=o},this.loggingEnabled=function(c){return a.isDefined(c)?(l=!!c,b):l},this.logToConsole=function(c){return a.isDefined(c)?(k=!!c,b):k},this.deleteHeaders=function(c){return a.isDefined(c)?(n=!!c,b):n},this.$get=["$injector",function(c){var d=null,i=function(b){if(p&&l){var i=c.get("$window"),j=c.get("$location"),k=c.get("$http");d=new Date;var o=a.extend({},e,b);f&&(o.url=j.absUrl()),g&&(o.timestamp=d.toISOString()),h&&(o.userAgent=i.navigator.userAgent);var r={headers:{"Content-Type":"text/plain"},withCredentials:!1};if(n){var s=Object.keys(k.defaults.headers.common).concat(Object.keys(k.defaults.headers.post));s=s.filter(function(a){return"Accept"!==a&&"Content-Type"!==a});for(var t=0;t (http://ajbrown.org)",
12 | "contributors": [
13 | {
14 | "name": "Milos Janjic",
15 | "email": "milosh012@gmail.com",
16 | "url": "https://github.com/milosh012",
17 | "contributions": 1,
18 | "additions": 7,
19 | "deletions": 6,
20 | "hireable": true
21 | },
22 | {
23 | "name": "Will McClellan",
24 | "email": null,
25 | "url": "https://github.com/willmcclellan",
26 | "contributions": 1,
27 | "additions": 1,
28 | "deletions": 0,
29 | "hireable": null
30 | },
31 | {
32 | "name": "Theodore Brockman",
33 | "email": null,
34 | "url": "https://github.com/tbrockman",
35 | "contributions": 1,
36 | "additions": 48,
37 | "deletions": 4,
38 | "hireable": null
39 | },
40 | {
41 | "name": "Guillaume Grégoire",
42 | "email": null,
43 | "url": "https://github.com/ggregoire",
44 | "contributions": 1,
45 | "additions": 3,
46 | "deletions": 3,
47 | "hireable": true
48 | },
49 | {
50 | "name": "Willian Ganzert Lopes",
51 | "email": null,
52 | "url": "https://github.com/willianganzert",
53 | "contributions": 1,
54 | "additions": 6,
55 | "deletions": 6,
56 | "hireable": true
57 | },
58 | {
59 | "name": "James Whitney",
60 | "email": "james@whitney.io",
61 | "url": "https://github.com/whitneyit",
62 | "contributions": 2,
63 | "additions": 32,
64 | "deletions": 26,
65 | "hireable": null
66 | },
67 | {
68 | "name": "Fkscorpion",
69 | "email": null,
70 | "url": "https://github.com/Fkscorpion",
71 | "contributions": 1,
72 | "additions": 4,
73 | "deletions": 4,
74 | "hireable": null
75 | },
76 | {
77 | "name": "Korotaev Alexander",
78 | "email": null,
79 | "url": "https://github.com/lekzd",
80 | "contributions": 1,
81 | "additions": 3,
82 | "deletions": 2,
83 | "hireable": null
84 | },
85 | {
86 | "name": "Elisha Terada",
87 | "email": "elishaterada@gmail.com",
88 | "url": "https://github.com/elishaterada",
89 | "contributions": 2,
90 | "additions": 122,
91 | "deletions": 15,
92 | "hireable": null
93 | },
94 | {
95 | "name": "Jason Skowronski",
96 | "email": null,
97 | "url": "https://github.com/mostlyjason",
98 | "contributions": 3,
99 | "additions": 50,
100 | "deletions": 25,
101 | "hireable": null
102 | },
103 | {
104 | "name": null,
105 | "email": null,
106 | "url": "https://github.com/varshneyjayant",
107 | "contributions": 3,
108 | "additions": 359,
109 | "deletions": 364,
110 | "hireable": null
111 | },
112 | {
113 | "name": "A.J. Brown",
114 | "email": "aj@ajbrown.org",
115 | "url": "https://github.com/ajbrown",
116 | "contributions": 45,
117 | "additions": 1624,
118 | "deletions": 669,
119 | "hireable": null
120 | },
121 | {
122 | "name": "Vin Halbwachs",
123 | "email": null,
124 | "url": "https://github.com/vhalbwachs",
125 | "contributions": 2,
126 | "additions": 56,
127 | "deletions": 52,
128 | "hireable": true
129 | },
130 | {
131 | "name": "Lukáš Marek",
132 | "email": "lukas.marek@gmail.com",
133 | "url": "https://github.com/krtek",
134 | "contributions": 1,
135 | "additions": 72,
136 | "deletions": 3,
137 | "hireable": true
138 | },
139 | {
140 | "name": "Brennon Bortz",
141 | "email": "brennon@brennonbortz.com",
142 | "url": "https://github.com/brennon",
143 | "contributions": 2,
144 | "additions": 19,
145 | "deletions": 4,
146 | "hireable": true
147 | }
148 | ],
149 | "keywords": [
150 | "angular",
151 | "logging",
152 | "logger",
153 | "log"
154 | ],
155 | "license": "MIT",
156 | "devDependencies": {
157 | "angular-mocks": "^1.5.6",
158 | "grunt": "^1.0.1",
159 | "grunt-browser-sync": "^2.2.0",
160 | "grunt-connect-proxy": "^0.2.0",
161 | "grunt-contrib-jshint": "^1.1.0",
162 | "grunt-contrib-uglify": "^2.1.0",
163 | "grunt-contrib-watch": "^1.0.0",
164 | "grunt-karma": "^2.0.0",
165 | "http-server": "^0.6.1",
166 | "jasmine-core": "^2.2.0",
167 | "karma": "^1.5.0",
168 | "karma-chrome-launcher": "^2.0.0",
169 | "karma-coverage": "^1.1.1",
170 | "karma-coveralls": "^1.1.2",
171 | "karma-firefox-launcher": "^1.0.0",
172 | "karma-jasmine": "^1.1.0",
173 | "karma-phantomjs-launcher": "^1.0.2",
174 | "karma-spec-reporter": "0.0.26",
175 | "protractor": "~0.17.0",
176 | "shelljs": "^0.2.6"
177 | },
178 | "scripts": {
179 | "start": "http-server -p 8000",
180 | "update-webdriver": "webdriver-manager update",
181 | "test": "grunt karma:unit",
182 | "test-single-run": "grunt karma:travis:run",
183 | "update-index-async": "node -e \"require('shelljs/global'); sed('-i', /\\/\\/@@NG_LOADER_START@@[\\s\\S]*\\/\\/@@NG_LOADER_END@@/, '//@@NG_LOADER_START@@\\n' + cat('bower_components/angular-loader/angular-loader.min.js') + '\\n//@@NG_LOADER_END@@', 'app/index-async.html');\""
184 | },
185 | "dependencies": {
186 | "angular": "^1.6.2"
187 | }
188 | }
189 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | // JSHint Default Configuration File (as on JSHint website)
3 | // See http://jshint.com/docs/ for more details
4 |
5 | "maxerr" : 50, // {int} Maximum error before stopping
6 |
7 | // Enforcing
8 | "bitwise" : true, // true: Prohibit bitwise operators (&, |, ^, etc.)
9 | "camelcase" : false, // true: Identifiers must be in camelCase
10 | "curly" : true, // true: Require {} for every new block or scope
11 | "eqeqeq" : true, // true: Require triple equals (===) for comparison
12 | "forin" : true, // true: Require filtering for..in loops with obj.hasOwnProperty()
13 | "freeze" : true, // true: prohibits overwriting prototypes of native objects such as Array, Date etc.
14 | "immed" : false, // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());`
15 | "indent" : 4, // {int} Number of spaces to use for indentation
16 | "latedef" : false, // true: Require variables/functions to be defined before being used
17 | "newcap" : false, // true: Require capitalization of all constructor functions e.g. `new F()`
18 | "noarg" : true, // true: Prohibit use of `arguments.caller` and `arguments.callee`
19 | "noempty" : true, // true: Prohibit use of empty blocks
20 | "nonbsp" : true, // true: Prohibit "non-breaking whitespace" characters.
21 | "nonew" : false, // true: Prohibit use of constructors for side-effects (without assignment)
22 | "plusplus" : false, // true: Prohibit use of `++` & `--`
23 | "quotmark" : false, // Quotation mark consistency:
24 | // false : do nothing (default)
25 | // true : ensure whatever is used is consistent
26 | // "single" : require single quotes
27 | // "double" : require double quotes
28 | "undef" : true, // true: Require all non-global variables to be declared (prevents global leaks)
29 | "unused" : true, // Unused variables:
30 | // true : all variables, last function parameter
31 | // "vars" : all variables only
32 | // "strict" : all variables, all function parameters
33 | "strict" : true, // true: Requires all functions run in ES5 Strict Mode
34 | "maxparams" : false, // {int} Max number of formal params allowed per function
35 | "maxdepth" : false, // {int} Max depth of nested blocks (within functions)
36 | "maxstatements" : false, // {int} Max number statements per function
37 | "maxcomplexity" : false, // {int} Max cyclomatic complexity per function
38 | "maxlen" : false, // {int} Max number of characters per line
39 | "varstmt" : false, // true: Disallow any var statements. Only `let` and `const` are allowed.
40 |
41 | // Relaxing
42 | "asi" : false, // true: Tolerate Automatic Semicolon Insertion (no semicolons)
43 | "boss" : false, // true: Tolerate assignments where comparisons would be expected
44 | "debug" : false, // true: Allow debugger statements e.g. browser breakpoints.
45 | "eqnull" : false, // true: Tolerate use of `== null`
46 | "es5" : false, // true: Allow ES5 syntax (ex: getters and setters)
47 | "esnext" : false, // true: Allow ES.next (ES6) syntax (ex: `const`)
48 | "moz" : false, // true: Allow Mozilla specific syntax (extends and overrides esnext features)
49 | // (ex: `for each`, multiple try/catch, function expression…)
50 | "evil" : false, // true: Tolerate use of `eval` and `new Function()`
51 | "expr" : false, // true: Tolerate `ExpressionStatement` as Programs
52 | "funcscope" : false, // true: Tolerate defining variables inside control statements
53 | "globalstrict" : false, // true: Allow global "use strict" (also enables 'strict')
54 | "iterator" : false, // true: Tolerate using the `__iterator__` property
55 | "lastsemic" : false, // true: Tolerate omitting a semicolon for the last statement of a 1-line block
56 | "laxbreak" : false, // true: Tolerate possibly unsafe line breakings
57 | "laxcomma" : false, // true: Tolerate comma-first style coding
58 | "loopfunc" : false, // true: Tolerate functions being defined in loops
59 | "multistr" : false, // true: Tolerate multi-line strings
60 | "noyield" : false, // true: Tolerate generator functions with no yield statement in them.
61 | "notypeof" : false, // true: Tolerate invalid typeof operator values
62 | "proto" : false, // true: Tolerate using the `__proto__` property
63 | "scripturl" : false, // true: Tolerate script-targeted URLs
64 | "shadow" : false, // true: Allows re-define variables later in code e.g. `var x=1; x=2;`
65 | "sub" : false, // true: Tolerate using `[]` notation when it can still be expressed in dot notation
66 | "supernew" : false, // true: Tolerate `new function () { ... };` and `new Object;`
67 | "validthis" : false, // true: Tolerate using this in a non-constructor function
68 |
69 | // Environments
70 | "browser" : true, // Web Browser (window, document, etc)
71 | "browserify" : false, // Browserify (node.js code in the browser)
72 | "couch" : false, // CouchDB
73 | "devel" : true, // Development/debugging (alert, confirm, etc)
74 | "dojo" : false, // Dojo Toolkit
75 | "jasmine" : false, // Jasmine
76 | "jquery" : false, // jQuery
77 | "mocha" : true, // Mocha
78 | "mootools" : false, // MooTools
79 | "node" : false, // Node.js
80 | "nonstandard" : false, // Widely adopted globals (escape, unescape, etc)
81 | "phantom" : false, // PhantomJS
82 | "prototypejs" : false, // Prototype and Scriptaculous
83 | "qunit" : false, // QUnit
84 | "rhino" : false, // Rhino
85 | "shelljs" : false, // ShellJS
86 | "typed" : false, // Globals for typed array constructions
87 | "worker" : false, // Web Workers
88 | "wsh" : false, // Windows Scripting Host
89 | "yui" : false, // Yahoo User Interface
90 |
91 | // Custom Globals
92 | "globals" : {} // additional predefined global variables
93 | }
94 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://travis-ci.org/ajbrown/angular-loggly-logger)
2 | [](https://coveralls.io/r/ajbrown/angular-loggly-logger?branch=master)
3 | [](https://gitter.im/ajbrown/angular-loggly-logger?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
4 |
5 |
6 | Angular Loggly Logger is a module which will decorate Angular's $log service,
7 | and provide a `LogglyLogger` service which can be used to manually send messages
8 | of any kind to the [Loggly](https://www.loggly.com) cloud log management service.
9 |
10 |
11 | ### Getting Started
12 |
13 | LogglyLogger can be installed with bower:
14 |
15 | ```
16 | bower install angular-loggly-logger
17 | ```
18 |
19 | Or with npm:
20 |
21 | ```
22 | npm install --save angular-loggly-logger
23 | ```
24 |
25 | Once configured (by including "logglyLogger" as a module dependency), the `$log`
26 | service will automatically be decorated, and all messages logged will be handled
27 | as normal as well as formated and passed to LogglyLogger.sendMessage.
28 | The plain text messages are sent into the "json.message" field with the decorated
29 | log while custom JSON objects are sent via "json.messageObj" field as Loggly
30 | only supports one type per field.
31 |
32 | To use both the decorated $log and the LogglyLogger service, you must first
33 | configure it with an inputToken, which is done via the LogglyLoggerProvider:
34 |
35 | ```javascript
36 | angular.module( 'myApp', [require('angular-loggly-logger')] )
37 |
38 | .config(["LogglyLoggerProvider", function( LogglyLoggerProvider ) {
39 | LogglyLoggerProvider.inputToken( '' );
40 | } ]);
41 |
42 | .run(["LogglyLogger", "$log", function( LogglyLogger, $log ) {
43 |
44 | //This will be sent to both the console and Loggly
45 | $log.info( "I'm a little teapot." );
46 |
47 | //This will be sent to loggly only
48 | LogglyLogger.sendMessage( { message : 'Short and Stout.' } );
49 | }])
50 |
51 | ```
52 |
53 | ### $log decoration
54 |
55 | When sent through the `$log` decorator, messages will be formatted as follows:
56 |
57 | ```javascript
58 |
59 | // Example: $log.warn( 'Danger! Danger!' );
60 | {
61 | level: "WARN",
62 | timestamp: "2014-05-01T13:10Z",
63 | msg: "Danger! Danger!",
64 | url: "https://github.com/ajbrown/angular-loggly-logger/demo/index.html",
65 | }
66 |
67 | // Example: $log.debug( 'User submitted something:', { foo: 'A.J', bar: 'Space' } )
68 |
69 | {
70 | level: "DEBUG",
71 | timestamp: "2014-05-01T13:18Z",
72 | msg: ["User submitted something", { foo: 'A.J.', bar: 'Space' }],
73 | url: "https://github.com/ajbrown/angular-loggly-logger/demo/index.html",
74 | }
75 | ```
76 |
77 | > However, 'url' and 'timestamp' are not included by default. You must enable those options in your application config (see below).
78 |
79 |
80 | Note that if you do not call `LogglyLoggerProvider.inputToken()` in a config method, messages will not be sent to loggly. At the moment, there is no warning -- your message is just ignored.
81 |
82 | ### Configuration
83 |
84 | The following configuration options are available.
85 |
86 | ```javascript
87 |
88 | LogglyLoggerProvider
89 |
90 | // set the logging level for messages sent to Loggly. Default is 'DEBUG',
91 | // which will send all log messages.
92 | .level( 'DEBUG' )
93 |
94 | // set the token of the loggly input to use. Must be set, or no logs
95 | // will be sent.
96 | .inputToken( '' )
97 |
98 | // set whether or not HTTPS should be used for sending messages. Default
99 | // is true
100 | .useHttps( true )
101 |
102 | // should the value of $location.absUrl() be sent as a "url" key in the
103 | // message object that's sent to loggly? Default is false.
104 | .includeUrl( false )
105 |
106 | // should the value of $window.navigator.userAgent be sent as a "userAgent" key in the
107 | // message object that's sent to loggly? Default is false.
108 | .includeUserAgent( false )
109 |
110 | // should the current timestamp be included? Default is false.
111 | .includeTimestamp( false )
112 |
113 | // set comma-seperated tags that should be included with the log events.
114 | // Default is "angular"
115 | .inputTag("angular,customTag")
116 |
117 | // Send console error stack traces to Loggly. Default is false.
118 | .sendConsoleErrors( false )
119 |
120 | // Toggle logging to console. When set to false, messages will not be
121 | // be passed along to the original $log methods. This makes it easy to
122 | // keep sending messages to Loggly in production without also sending them
123 | // to the console. Default is true.
124 | .logToConsole( true )
125 |
126 | //Toggle delete other headers. If there are any other headers than Accept
127 | //and Content-Type in request, the browser will first send pre-flight OPTIONS
128 | //request.
129 | //Turn this on if you see HTTP 405 errors in console.
130 | .deleteHeaders( false )
131 |
132 | // Custom labels for standard log fields. Use this to customize your log
133 | // message format or to shorten your logging payload. All available labels
134 | // are listed in this example.
135 | .labels({
136 | col: 'c',
137 | level: 'lvl',
138 | line: 'l',
139 | logger: 'lgr',
140 | message: 'msg',
141 | stack: 'stk',
142 | timestamp: 'ts',
143 | url: 'url',
144 | userAgent: 'userAgent'
145 | })
146 |
147 | ```
148 |
149 | ### Sending JSON Fields
150 |
151 | You can also default some "extra/default" information to be sent with each log message. When this is set, `LogglyLogger`
152 | will include the key/values provided with all messages, plus the data to be sent for each specific logging request.
153 |
154 | ```javascript
155 |
156 | LogglyLoggerProvider.fields( { appVersion: 1.1.0, browser: 'Chrome' } );
157 |
158 | //...
159 |
160 | $log.warn( 'Danger! Danger!' )
161 |
162 | >> { appVersion: 1.1.0, browser: 'Chrome', level: 'WARN', message: 'Danger! Danger', url: 'http://google.com' }
163 | ```
164 |
165 | Extra fields can also be added at runtime using the `LogglyLogger` service:
166 |
167 | ```javascript
168 | app.controller( 'MainCtrl', ["$scope", "$log", "LogglyLogger", function( $scope, $log, LogglyLogger ) {
169 |
170 | logglyLogger.fields( { username: "foobar" } );
171 |
172 | //...
173 |
174 | $log.info( 'All is good!' );
175 |
176 | >> { appVersion: 1.1.0, browser: 'Chrome', username: 'foobar', level: 'WARN', message: 'All is good', url: 'http://google.com' }
177 | }])
178 |
179 | ```
180 |
181 |
182 | Beware that when using `setExtra` with `LogglyLogger.sendMessage( obj )`, any properties in your `obj` that are the same as your `extra` will be overwritten.
183 |
184 | ## ChangeLog
185 | - v0.2.2 - Fixes preflight cross origin issues.
186 | - v0.2.3 - Fixes npm install issues related to Bower.
187 | - v0.2.4 - Adds customizable labels, error stacktraces, and user-agent logging.
188 |
189 |
190 | ## Contributing
191 |
192 | Contributions are awesome, welcomed, and wanted. Please contribute ideas by [opening a new issue](http://github.com/ajbrown/angular-loggy-logger/issues), or code by creating a new pull request. Please make sure your pull request targets the "develop" branch.
193 |
--------------------------------------------------------------------------------
/angular-loggly-logger.js:
--------------------------------------------------------------------------------
1 | /**
2 | * logglyLogger is a module which will send your log messages to a configured
3 | * [Loggly](http://loggly.com) connector.
4 | *
5 | * Major credit should go to Thomas Burleson, who's highly informative blog
6 | * post on [Enhancing AngularJs Logging using Decorators](http://bit.ly/1pOI0bb)
7 | * provided the foundation (if not the majority of the brainpower) for this
8 | * module.
9 | *
10 | * @version 0.3.0
11 | * @author A.J. Brown
12 | * @license MIT License, http://www.opensource.org/licenses/MIT
13 | */
14 | (function( angular ) {
15 | "use strict";
16 |
17 | angular.module( 'logglyLogger.logger', [] )
18 | .provider( 'LogglyLogger', function() {
19 | var self = this;
20 |
21 | var logLevels = [ 'DEBUG', 'INFO', 'WARN', 'ERROR' ];
22 |
23 | var https = true;
24 | var extra = {};
25 | var includeCurrentUrl = false;
26 | var includeTimestamp = false;
27 | var includeUserAgent = false;
28 | var tag = null;
29 | var sendConsoleErrors = false;
30 | var logToConsole = true;
31 | var loggingEnabled = true;
32 | var labels = {};
33 | var deleteHeaders = false;
34 |
35 | // The minimum level of messages that should be sent to loggly.
36 | var level = 0;
37 |
38 | var token = null;
39 | var endpoint = '://logs-01.loggly.com/inputs/';
40 |
41 | var buildUrl = function () {
42 | return (https ? 'https' : 'http') + endpoint + token + '/tag/' + (tag ? tag : 'AngularJS' ) + '/';
43 | };
44 |
45 | this.setExtra = function (d) {
46 | extra = d;
47 | return self;
48 | };
49 |
50 | this.fields = function ( d ) {
51 | if( angular.isDefined( d ) ) {
52 | angular.extend(extra, d);
53 | return self;
54 | }
55 |
56 | return extra;
57 | };
58 |
59 | this.labels = function(l) {
60 | if (angular.isObject(l)) {
61 | labels = l;
62 | return self;
63 | }
64 |
65 | return labels;
66 | };
67 |
68 | this.inputToken = function ( s ) {
69 | if (angular.isDefined(s)) {
70 | token = s;
71 | return self;
72 | }
73 |
74 | return token;
75 | };
76 |
77 | this.useHttps = function (flag) {
78 | if (angular.isDefined(flag)) {
79 | https = !!flag;
80 | return self;
81 | }
82 |
83 | return https;
84 | };
85 |
86 | this.includeUrl = function (flag) {
87 | if (angular.isDefined(flag)) {
88 | includeCurrentUrl = !!flag;
89 | return self;
90 | }
91 |
92 | return includeCurrentUrl;
93 | };
94 |
95 | this.includeTimestamp = function (flag) {
96 | if (angular.isDefined(flag)) {
97 | includeTimestamp = !!flag;
98 | return self;
99 | }
100 |
101 | return includeTimestamp;
102 | };
103 |
104 | this.includeUserAgent = function (flag) {
105 | if (angular.isDefined(flag)) {
106 | includeUserAgent = !!flag;
107 | return self;
108 | }
109 |
110 | return includeUserAgent;
111 | };
112 |
113 | this.inputTag = function (usrTag){
114 | if (angular.isDefined(usrTag)) {
115 | tag = usrTag;
116 | return self;
117 | }
118 |
119 | return tag;
120 | };
121 |
122 | this.sendConsoleErrors = function (flag){
123 | if (angular.isDefined(flag)) {
124 | sendConsoleErrors = !!flag;
125 | return self;
126 | }
127 |
128 | return sendConsoleErrors;
129 | };
130 |
131 | this.level = function ( name ) {
132 |
133 | if( angular.isDefined( name ) ) {
134 | var newLevel = logLevels.indexOf( name.toUpperCase() );
135 |
136 | if( newLevel < 0 ) {
137 | throw "Invalid logging level specified: " + name;
138 | } else {
139 | level = newLevel;
140 | }
141 |
142 | return self;
143 | }
144 |
145 | return logLevels[level];
146 | };
147 |
148 | this.isLevelEnabled = function( name ) {
149 | return logLevels.indexOf( name.toUpperCase() ) >= level;
150 | };
151 |
152 | this.loggingEnabled = function (flag) {
153 | if (angular.isDefined(flag)) {
154 | loggingEnabled = !!flag;
155 | return self;
156 | }
157 |
158 | return loggingEnabled;
159 | };
160 |
161 |
162 | this.logToConsole = function (flag) {
163 | if (angular.isDefined(flag)) {
164 | logToConsole = !!flag;
165 | return self;
166 | }
167 |
168 | return logToConsole;
169 | };
170 |
171 | this.deleteHeaders = function (flag) {
172 | if (angular.isDefined(flag)) {
173 | deleteHeaders = !!flag;
174 | return self;
175 | }
176 |
177 | return deleteHeaders;
178 | };
179 |
180 |
181 |
182 | this.$get = [ '$injector', function ($injector) {
183 |
184 | var lastLog = null;
185 |
186 |
187 | /**
188 | * Send the specified data to loggly as a json message.
189 | * @param data
190 | */
191 | var sendMessage = function (data) {
192 | //If a token is not configured, don't do anything.
193 | if (!token || !loggingEnabled) {
194 | return;
195 | }
196 |
197 | //TODO we're injecting this here to resolve circular dependency issues. Is this safe?
198 | var $window = $injector.get( '$window' );
199 | var $location = $injector.get( '$location' );
200 | //we're injecting $http
201 | var $http = $injector.get( '$http' );
202 |
203 | lastLog = new Date();
204 |
205 | var sentData = angular.extend({}, extra, data);
206 |
207 | if (includeCurrentUrl) {
208 | sentData.url = $location.absUrl();
209 | }
210 |
211 | if( includeTimestamp ) {
212 | sentData.timestamp = lastLog.toISOString();
213 | }
214 |
215 | if( includeUserAgent ) {
216 | sentData.userAgent = $window.navigator.userAgent;
217 | }
218 |
219 | //Loggly's API doesn't send us cross-domain headers, so we can't interact directly
220 | //Set header
221 | var config = {
222 | headers: {
223 | 'Content-Type': 'text/plain'
224 | },
225 | withCredentials: false
226 | };
227 |
228 | if (deleteHeaders) {
229 | //Delete other headers - this tells browser it's no need to pre-flight OPTIONS request
230 | var headersToDelete = Object.keys($http.defaults.headers.common).concat(Object.keys($http.defaults.headers.post));
231 | headersToDelete = headersToDelete.filter(function(item) {
232 | return item !== 'Accept' && item !== 'Content-Type';
233 | });
234 |
235 | for (var index = 0; index < headersToDelete.length; index++) {
236 | var headerName = headersToDelete[index];
237 | config.headers[headerName] = undefined;
238 | }
239 | }
240 |
241 | // Apply labels
242 | for (var label in labels) {
243 | if (label in sentData) {
244 | sentData[labels[label]] = sentData[label];
245 | delete sentData[label];
246 | }
247 | }
248 |
249 | //Ajax call to send data to loggly
250 | $http.post(buildUrl(),sentData,config).catch(console.error);
251 | };
252 |
253 | var attach = function() {
254 | };
255 |
256 | var inputToken = function(s) {
257 | if (angular.isDefined(s)) {
258 | token = s;
259 | }
260 |
261 | return token;
262 | };
263 |
264 | return {
265 | lastLog: function(){ return lastLog; },
266 | sendConsoleErrors: function(){ return sendConsoleErrors; },
267 | level : function() { return level; },
268 | loggingEnabled: self.loggingEnabled,
269 | isLevelEnabled : self.isLevelEnabled,
270 | inputTag: self.inputTag,
271 | attach: attach,
272 | sendMessage: sendMessage,
273 | logToConsole: logToConsole,
274 | inputToken: inputToken,
275 |
276 | /**
277 | * Get or set the fields to be sent with all logged events.
278 | * @param d
279 | * @returns {*}
280 | */
281 | fields: function( d ) {
282 | if( angular.isDefined( d ) ) {
283 | self.fields( d );
284 | }
285 | return self.fields();
286 | }
287 | };
288 | }];
289 |
290 | } );
291 |
292 |
293 | angular.module( 'logglyLogger', ['logglyLogger.logger'] )
294 | .config( [ '$provide', function( $provide ) {
295 |
296 | $provide.decorator('$log', [ "$delegate", '$injector', function ( $delegate, $injector ) {
297 |
298 | var logger = $injector.get('LogglyLogger');
299 |
300 | // install a window error handler
301 | if(logger.sendConsoleErrors() === true) {
302 | var _onerror = window.onerror;
303 |
304 | //send console error messages to Loggly
305 | window.onerror = function (msg, url, line, col, error) {
306 | logger.sendMessage({
307 | level : 'ERROR',
308 | message: msg,
309 | line: line,
310 | col: col,
311 | stack: error && error.stack
312 | });
313 |
314 | if (_onerror && typeof _onerror === 'function') {
315 | _onerror.apply(window, arguments);
316 | }
317 | };
318 | }
319 |
320 | var wrapLogFunction = function(logFn, level, loggerName) {
321 |
322 | var wrappedFn = function () {
323 | var args = Array.prototype.slice.call(arguments);
324 |
325 | if(logger.logToConsole) {
326 | logFn.apply(null, args);
327 | }
328 |
329 | // Skip messages that have a level that's lower than the configured level for this logger.
330 | if(!logger.loggingEnabled() || !logger.isLevelEnabled( level ) ) {
331 | return;
332 | }
333 |
334 | var msg = (args.length === 1 ? args[0] : args) || {};
335 | var sending = { level: level };
336 |
337 | if(angular.isDefined(msg.stack) || (angular.isDefined(msg[0]) && angular.isDefined(msg[0].stack))) {
338 | //handling console errors
339 | if(logger.sendConsoleErrors() === true) {
340 | sending.message = msg.message ? msg.message : (msg[0] && msg[0].message) ? msg[0].message : null;
341 | sending.stack = msg.stack || msg[0].stack;
342 | }
343 | else {
344 | return;
345 | }
346 | }
347 | else if(angular.isObject(msg)) {
348 | //handling JSON objects
349 | sending = angular.extend({}, msg, sending);
350 | }
351 | else{
352 | //sending plain text
353 | sending.message = msg;
354 | }
355 |
356 | if( loggerName ) {
357 | sending.logger = msg;
358 | }
359 |
360 | //Send the message to through the loggly sender
361 | logger.sendMessage( sending );
362 | };
363 |
364 | wrappedFn.logs = [];
365 |
366 | return wrappedFn;
367 | };
368 |
369 | var _$log = (function ($delegate) {
370 | return {
371 | log: $delegate.log,
372 | info: $delegate.info,
373 | warn: $delegate.warn,
374 | error: $delegate.error
375 | };
376 | })($delegate);
377 |
378 | var getLogger = function ( name ) {
379 | return {
380 | log: wrapLogFunction( _$log.log, 'INFO', name ),
381 | debug: wrapLogFunction( _$log.debug, 'DEBUG', name ),
382 | info: wrapLogFunction( _$log.info, 'INFO', name ),
383 | warn: wrapLogFunction( _$log.warn, 'WARN', name ),
384 | error: wrapLogFunction( _$log.error, 'ERROR', name )
385 | };
386 | };
387 |
388 | //wrap the existing API
389 | $delegate.log = wrapLogFunction($delegate.log, 'INFO');
390 | $delegate.debug = wrapLogFunction($delegate.debug, 'DEBUG');
391 | $delegate.info = wrapLogFunction($delegate.info, 'INFO');
392 | $delegate.warn = wrapLogFunction($delegate.warn, 'WARN');
393 | $delegate.error = wrapLogFunction($delegate.error, 'ERROR');
394 |
395 | //Add some methods
396 | $delegate.getLogger = getLogger;
397 |
398 | return $delegate;
399 | }]);
400 |
401 | }]);
402 |
403 |
404 |
405 | })(window.angular);
406 |
--------------------------------------------------------------------------------
/test/unit/logglySenderSpec.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /* jasmine specs for services go here */
4 |
5 | describe('logglyLogger Module:', function() {
6 | var logglyLoggerProvider,
7 | levels = ['DEBUG', 'INFO', 'WARN', 'ERROR'],
8 | realOnerror, mockOnerror;
9 |
10 | beforeEach(function () {
11 | // Karma defines window.onerror to kill the test when it's called, so stub out window.onerror
12 | // Jasmine still wraps all tests in a try/catch, so tests that throw errors will still be handled gracefully
13 | realOnerror = window.onerror;
14 | mockOnerror = jasmine.createSpy();
15 | window.onerror = mockOnerror;
16 |
17 | // Initialize the service provider
18 | // by injecting it to a fake module's config block
19 | var fakeModule = angular.module('testing.harness', ['logglyLogger'], function () {});
20 | fakeModule.config( function(LogglyLoggerProvider) {
21 | logglyLoggerProvider = LogglyLoggerProvider;
22 | logglyLoggerProvider.sendConsoleErrors(true)
23 | });
24 |
25 | // Initialize test.app injector
26 | module('logglyLogger', 'testing.harness');
27 |
28 | // Kickstart the injectors previously registered
29 | // with calls to angular.mock.module
30 | inject(function() {});
31 | });
32 |
33 | afterEach(function() {
34 | window.onerror = realOnerror;
35 | });
36 |
37 | describe( 'LogglyLoggerProvider', function() {
38 | it( 'can have a logging level configured', function() {
39 |
40 | for( var i in levels ) {
41 | logglyLoggerProvider.level( levels[i] );
42 | expect( logglyLoggerProvider.level() ).toEqual( levels[i] );
43 | }
44 | });
45 |
46 | it( 'will throw an exception if an invalid level is supplied', function() {
47 |
48 | expect( function() { logglyLoggerProvider.level('TEST') } ).toThrow();
49 | });
50 |
51 | it( 'can determine if a given level is enabled', function() {
52 | for( var a in levels ) {
53 |
54 | logglyLoggerProvider.level( levels[a] );
55 |
56 | for( var b in levels ) {
57 | expect( logglyLoggerProvider.isLevelEnabled( levels[b] )).toBe( b >= a );
58 | }
59 | }
60 | });
61 |
62 | it( 'can specify extra fields to be sent with each log message', function() {
63 | var extra = { "test": "extra" };
64 |
65 | logglyLoggerProvider.fields( extra );
66 |
67 | expect( logglyLoggerProvider.fields()).toEqual( extra );
68 |
69 | });
70 |
71 | });
72 |
73 | describe( 'LogglyLogger', function() {
74 | var token = 'test123456',
75 | tag = 'logglyLogger',
76 | message, service, $log, $httpBackend, $http;
77 |
78 | beforeEach(function () {
79 | message = {message: 'A test message'};
80 |
81 | inject(function ($injector) {
82 | $log = $injector.get('$log');
83 | $httpBackend = $injector.get('$httpBackend');
84 | $http = $injector.get('$http');
85 | service = $injector.get('LogglyLogger');
86 | service.attach();
87 | });
88 | });
89 |
90 | afterEach(function () {
91 | $httpBackend.verifyNoOutstandingExpectation();
92 | $httpBackend.verifyNoOutstandingRequest();
93 | });
94 |
95 | it('should be registered', function () {
96 | expect(service).not.toBe(null);
97 | });
98 |
99 | it('will not send a message to loggly if a token is not specified', function () {
100 | var url = 'https://logs-01.loggly.com';
101 | var forbiddenCallTriggered = false;
102 | $httpBackend
103 | .when(url)
104 | .respond(function () {
105 | forbiddenCallTriggered = true;
106 | return [400, ''];
107 | });
108 |
109 | service.sendMessage("A test message");
110 | // Let test fail when request was triggered.
111 | expect(forbiddenCallTriggered).toBe(false);
112 | });
113 |
114 | it('will send a message to loggly only when properly configured', function () {
115 | var expectMessage = { message: 'A test message' };
116 | var tag = 'logglyLogger';
117 | var testURL = 'https://logs-01.loggly.com/inputs/test123456/tag/logglyLogger/';
118 | var generatedURL;
119 |
120 | logglyLoggerProvider.inputToken(token);
121 | logglyLoggerProvider.includeUrl(false);
122 | logglyLoggerProvider.inputTag(tag);
123 |
124 | $httpBackend
125 | .expectPOST(testURL, expectMessage)
126 | .respond(function (method, url, data) {
127 | generatedURL = url;
128 | return [200, "", {}];
129 | });
130 |
131 | service.sendMessage(message);
132 | $httpBackend.flush();
133 |
134 | expect(generatedURL).toEqual(testURL);
135 | });
136 |
137 | it('will use http if useHttps is set to false', function () {
138 | var testURL = 'http://logs-01.loggly.com/inputs/test123456/tag/AngularJS/';
139 | var generatedURL;
140 |
141 | logglyLoggerProvider.inputToken(token);
142 | logglyLoggerProvider.useHttps(false);
143 | logglyLoggerProvider.includeUrl(false);
144 |
145 | $httpBackend
146 | .expectPOST(testURL, message)
147 | .respond(function (method, url, data) {
148 | generatedURL = new URL(url);
149 | return [200, "", {}];
150 | });
151 |
152 | service.sendMessage(message);
153 |
154 | $httpBackend.flush();
155 |
156 | expect(generatedURL.protocol).toEqual('http:');
157 |
158 | });
159 |
160 | it('will include the current url if includeUrl() is not set to false', function () {
161 | var expectMessage = angular.extend({}, message, { url: 'http://bloggly.com' });
162 | var testURL = 'https://logs-01.loggly.com/inputs/test123456/tag/AngularJS/';
163 | var payload;
164 |
165 | inject(function ($injector) {
166 | // mock browser url
167 | $injector.get('$browser').url('http://bloggly.com');
168 | });
169 |
170 | logglyLoggerProvider.inputToken( token );
171 | logglyLoggerProvider.includeUrl( true );
172 |
173 | $httpBackend
174 | .expectPOST(testURL, expectMessage)
175 | .respond(function (method, url, data) {
176 | payload = JSON.parse(data);
177 | return [200, "", {}];
178 | });
179 |
180 | service.sendMessage( message );
181 |
182 | $httpBackend.flush();
183 | expect(payload.url).toEqual('http://bloggly.com');
184 |
185 | });
186 |
187 | it('will include the current userAgent if includeUserAgent() is not set to false', function () {
188 | var expectMessage = angular.extend({}, message, { userAgent: window.navigator.userAgent });
189 | var testURL = 'https://logs-01.loggly.com/inputs/test123456/tag/AngularJS/';
190 | var payload;
191 |
192 | logglyLoggerProvider.inputToken( token );
193 | logglyLoggerProvider.includeUserAgent( true );
194 |
195 | $httpBackend
196 | .expectPOST(testURL, expectMessage)
197 | .respond(function (method, url, data) {
198 | payload = JSON.parse(data);
199 | return [200, "", {}];
200 | });
201 |
202 | service.sendMessage( message );
203 |
204 | $httpBackend.flush();
205 | expect(payload.userAgent).toEqual(window.navigator.userAgent);
206 |
207 | });
208 |
209 | it( 'can set extra fields using the fields method', function() {
210 | var extra = { appVersion: '1.1.0', browser: 'Chrome' };
211 |
212 | expect( service.fields( extra )).toEqual( extra );
213 | expect( service.fields() ).toEqual( extra );
214 | });
215 |
216 |
217 | it( 'will include extra fields if set via provider and service', function() {
218 | var payload, payload2;
219 | var extra = { appVersion: '1.1.0', browser: 'Chrome' };
220 | var expectMessage = angular.extend({}, message, extra);
221 | var testURL = 'https://logs-01.loggly.com/inputs/test123456/tag/AngularJS/';
222 |
223 | logglyLoggerProvider.inputToken( token );
224 |
225 |
226 | logglyLoggerProvider.fields( extra );
227 | $httpBackend
228 | .expectPOST(testURL, expectMessage)
229 | .respond(function (method, url, data) {
230 | payload = JSON.parse(data);
231 | return [200, "", {}];
232 | });
233 | service.sendMessage(message);
234 |
235 | $httpBackend.flush();
236 | expect(payload).toEqual(expectMessage);
237 |
238 | var expectMessage2 = angular.extend({}, message, { appVersion: '1.1.0', browser: 'Chrome', username: 'baldrin' });
239 |
240 | service.fields({username: "baldrin"});
241 | $httpBackend
242 | .expectPOST(testURL, expectMessage2)
243 | .respond(function (method, url, data) {
244 | payload2 = JSON.parse(data);
245 | return [200, "", {}];
246 | });
247 | service.sendMessage(message);
248 |
249 | $httpBackend.flush();
250 | expect(payload2).toEqual(expectMessage2);
251 | });
252 |
253 | it( 'will include extra fields if set via the service', function() {
254 | var payload;
255 | var testURL = 'https://logs-01.loggly.com/inputs/test123456/tag/AngularJS/';
256 | var extra = { appVersion: '1.1.0', browser: 'Chrome' };
257 | var expectMessage = angular.extend({}, message, extra);
258 |
259 | logglyLoggerProvider.inputToken( token );
260 | logglyLoggerProvider.fields( extra );
261 |
262 | $httpBackend
263 | .expectPOST(testURL, expectMessage)
264 | .respond(function (method, url, data) {
265 | payload = JSON.parse(data);
266 | return [200, "", {}];
267 | });
268 |
269 | service.sendMessage(message);
270 |
271 | $httpBackend.flush();
272 | expect(payload).toEqual(expectMessage);
273 | });
274 |
275 | it( '$log has a logglySender attached', function() {
276 | var testURL = 'https://logs-01.loggly.com/inputs/test123456/tag/AngularJS/';
277 | var payload, expectMessage;
278 |
279 | logglyLoggerProvider.inputToken( token );
280 | logglyLoggerProvider.includeUrl( false );
281 |
282 | angular.forEach( levels, function (level) {
283 | expectMessage = angular.extend({}, message, { level: level });
284 | $httpBackend
285 | .expectPOST(testURL, expectMessage)
286 | .respond(function (method, url, data) {
287 | payload = JSON.parse(data);
288 | return [200, "", {}];
289 | });
290 | $log[level.toLowerCase()].call($log, message);
291 | $httpBackend.flush();
292 | expect(payload.level).toEqual(level);
293 | });
294 | });
295 |
296 | it( 'will not send messages for levels that are not enabled', function() {
297 | spyOn(service, 'sendMessage').and.callThrough();
298 |
299 | for( var a in levels ) {
300 |
301 | logglyLoggerProvider.level( levels[a] );
302 |
303 | for( var b in levels ) {
304 |
305 | $log[levels[b].toLowerCase()].call($log, message.message);
306 | if( b >= a ) {
307 | expect(service.sendMessage).toHaveBeenCalled();
308 | } else {
309 | expect(service.sendMessage).not.toHaveBeenCalled();
310 | }
311 |
312 | service.sendMessage.calls.reset();
313 | }
314 | }
315 | });
316 |
317 | it( 'will not send messages if logs are not enabled', function() {
318 | var url = 'https://logs-01.loggly.com/inputs/' + token;
319 | var tag = 'logglyLogger';
320 |
321 | logglyLoggerProvider.inputToken(token);
322 | logglyLoggerProvider.includeUrl(false);
323 | logglyLoggerProvider.loggingEnabled(false);
324 | logglyLoggerProvider.inputTag(tag);
325 |
326 | var forbiddenCallTriggered = false;
327 | $httpBackend
328 | .when(url)
329 | .respond(function () {
330 | forbiddenCallTriggered = true;
331 | return [400, ''];
332 | });
333 | service.sendMessage("A test message");
334 | // Let test fail when request was triggered.
335 | expect(forbiddenCallTriggered).toBe(false);
336 | });
337 |
338 | it( 'will disable logs after config had them enabled and not send messages', function() {
339 | var tag = 'logglyLogger';
340 | var testURL = 'https://logs-01.loggly.com/inputs/test123456/tag/logglyLogger/';
341 | var generatedURL;
342 |
343 | logglyLoggerProvider.inputToken(token);
344 | logglyLoggerProvider.includeUrl(false);
345 | logglyLoggerProvider.loggingEnabled(true);
346 | logglyLoggerProvider.inputTag(tag);
347 |
348 | $httpBackend
349 | .expectPOST(testURL, message)
350 | .respond(function (method, url, data) {
351 | generatedURL = url;
352 | return [200, "", {}];
353 | });
354 |
355 | service.sendMessage(message);
356 | $httpBackend.flush();
357 | expect(generatedURL).toEqual(testURL);
358 | });
359 |
360 | it( 'will not fail if the logged message is null or undefined', function() {
361 | var undefinedMessage;
362 | var nullMessage = null;
363 |
364 | expect( function() {
365 | $log.debug( undefinedMessage );
366 | }).not.toThrow();
367 |
368 | expect( function() {
369 | $log.debug( nullMessage );
370 | }).not.toThrow();
371 | });
372 |
373 | it( 'can update the Loggly token', function() {
374 | logglyLoggerProvider.inputToken('');
375 | service.inputToken('foo');
376 | expect(logglyLoggerProvider.inputToken()).toEqual('foo');
377 | });
378 |
379 | it('will override labels as specified', function () {
380 | var expectMessage = { msg: message.message };
381 | var testURL = 'https://logs-01.loggly.com/inputs/test123456/tag/AngularJS/';
382 |
383 | logglyLoggerProvider.inputToken( token );
384 | logglyLoggerProvider.labels({
385 | message: 'msg'
386 | });
387 |
388 | $httpBackend
389 | .whenPOST(testURL)
390 | .respond(function (method, url, data) {
391 | expect(JSON.parse(data)).toEqual(expectMessage);
392 | return [200, "", {}];
393 | });
394 |
395 | service.sendMessage( message );
396 |
397 | $httpBackend.flush();
398 | });
399 |
400 | it('should log console errors if sendConsoleErrors() is not false', function() {
401 | var error = new Error("some error");
402 | var expectMessage = {level: 'ERROR', message: error.message, line: 1, col: 2, stack: error.stack};
403 | var testURL = 'https://logs-01.loggly.com/inputs/test123456/tag/AngularJS/';
404 |
405 | logglyLoggerProvider.inputToken(token);
406 |
407 | $httpBackend
408 | .expectPOST(testURL, expectMessage)
409 | .respond(function () {
410 | return [200, "", {}];
411 | });
412 |
413 | window.onerror(error.message, "foo.com", 1, 2, error);
414 |
415 | // Ensure the preexisting window.onerror is called
416 | expect(mockOnerror).toHaveBeenCalled();
417 |
418 | $httpBackend.flush();
419 | });
420 |
421 | it('should keep http headers if deleteHeaders is set to false', function() {
422 | var testURL = 'https://logs-01.loggly.com/inputs/test123456/tag/AngularJS/';
423 | $http.defaults.headers.common.Authorization = 'token';
424 | logglyLoggerProvider.inputToken(token);
425 |
426 | $httpBackend
427 | .expectPOST(testURL, {}, function(headers) {
428 | return headers['Authorization'] === 'token';
429 | })
430 | .respond(function () {
431 | return [200, "", {}];
432 | });
433 |
434 | service.sendMessage("A test message");
435 |
436 | $httpBackend.flush();
437 | });
438 |
439 | it('should delete http headers if deleteHeaders is set to true', function() {
440 | var testURL = 'https://logs-01.loggly.com/inputs/test123456/tag/AngularJS/';
441 | $http.defaults.headers.common.Authorization = 'token';
442 | logglyLoggerProvider.deleteHeaders(true);
443 | logglyLoggerProvider.inputToken(token);
444 |
445 | $httpBackend
446 | .expectPOST(testURL, {}, function(headers) {
447 | return headers['Authorization'] === undefined;
448 | })
449 | .respond(function () {
450 | return [200, "", {}];
451 | });
452 |
453 | service.sendMessage("A test message");
454 |
455 | $httpBackend.flush();
456 | });
457 | });
458 | });
459 |
--------------------------------------------------------------------------------