loggingManagerTests.js 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. /*
  2. * decaffeinate suggestions:
  3. * DS102: Remove unnecessary code created because of implicit returns
  4. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  5. */
  6. const SandboxedModule = require("sandboxed-module");
  7. const chai = require("chai");
  8. const path = require("path");
  9. const sinon = require("sinon");
  10. const sinonChai = require("sinon-chai");
  11. chai.use(sinonChai);
  12. chai.should();
  13. const modulePath = path.join(__dirname, '../../logging-manager.js');
  14. describe('LoggingManager', function() {
  15. beforeEach(function() {
  16. this.start = Date.now();
  17. this.clock = sinon.useFakeTimers(this.start);
  18. this.captureException = sinon.stub();
  19. this.mockBunyanLogger = {
  20. debug: sinon.stub(),
  21. error: sinon.stub(),
  22. fatal: sinon.stub(),
  23. info: sinon.stub(),
  24. level: sinon.stub(),
  25. warn: sinon.stub()
  26. };
  27. this.mockRavenClient = {
  28. captureException: this.captureException,
  29. once: sinon.stub().yields()
  30. };
  31. this.LoggingManager = SandboxedModule.require(modulePath, {
  32. globals: { console },
  33. requires: {
  34. bunyan: (this.Bunyan = {createLogger: sinon.stub().returns(this.mockBunyanLogger)}),
  35. raven: (this.Raven = {Client: sinon.stub().returns(this.mockRavenClient)}),
  36. request: (this.Request = sinon.stub()),
  37. },
  38. });
  39. this.loggerName = "test";
  40. this.logger = this.LoggingManager.initialize(this.loggerName);
  41. this.logger.initializeErrorReporting("test_dsn");
  42. });
  43. afterEach(function() {
  44. this.clock.restore();
  45. });
  46. describe('initialize', function() {
  47. beforeEach(function() {
  48. this.checkLogLevelStub = sinon.stub(this.LoggingManager.prototype, "checkLogLevel");
  49. this.Bunyan.createLogger.reset();
  50. });
  51. afterEach(function () {
  52. this.checkLogLevelStub.restore()
  53. })
  54. describe("not in production", function() {
  55. beforeEach(function() {
  56. this.logger = this.LoggingManager.initialize(this.loggerName)
  57. });
  58. it('should default to log level debug', function() {
  59. this.Bunyan.createLogger.should.have.been.calledWithMatch({level: "debug"});
  60. });
  61. it('should not run checkLogLevel', function() {
  62. this.checkLogLevelStub.should.not.have.been.called;
  63. });
  64. });
  65. describe("in production", function() {
  66. beforeEach(function() {
  67. process.env.NODE_ENV = 'production';
  68. this.logger = this.LoggingManager.initialize(this.loggerName)
  69. });
  70. afterEach(() => delete process.env.NODE_ENV);
  71. it('should default to log level warn', function() {
  72. this.Bunyan.createLogger.should.have.been.calledWithMatch({level: "warn"});
  73. });
  74. it('should run checkLogLevel', function() {
  75. this.checkLogLevelStub.should.have.been.calledOnce;
  76. });
  77. describe('after 1 minute', () =>
  78. it('should run checkLogLevel again', function() {
  79. this.clock.tick(61*1000);
  80. this.checkLogLevelStub.should.have.been.calledTwice;
  81. })
  82. );
  83. describe('after 2 minutes', () =>
  84. it('should run checkLogLevel again', function() {
  85. this.clock.tick(121*1000);
  86. this.checkLogLevelStub.should.have.been.calledThrice;
  87. })
  88. );
  89. });
  90. describe("when LOG_LEVEL set in env", function() {
  91. beforeEach(function() {
  92. process.env.LOG_LEVEL = "trace";
  93. this.LoggingManager.initialize();
  94. });
  95. afterEach(() => delete process.env.LOG_LEVEL);
  96. it("should use custom log level", function() {
  97. this.Bunyan.createLogger.should.have.been.calledWithMatch({level: "trace"});
  98. });
  99. });
  100. });
  101. describe('bunyan logging', function() {
  102. beforeEach(function() {
  103. this.logArgs = [ {foo: "bar"}, "foo", "bar" ];});
  104. it('should log debug', function() {
  105. this.logger.debug(this.logArgs);
  106. this.mockBunyanLogger.debug.should.have.been.calledWith(this.logArgs);
  107. });
  108. it('should log error', function() {
  109. this.logger.error(this.logArgs);
  110. this.mockBunyanLogger.error.should.have.been.calledWith(this.logArgs);
  111. });
  112. it('should log fatal', function() {
  113. this.logger.fatal(this.logArgs);
  114. this.mockBunyanLogger.fatal.should.have.been.calledWith(this.logArgs);
  115. });
  116. it('should log info', function() {
  117. this.logger.info(this.logArgs);
  118. this.mockBunyanLogger.info.should.have.been.calledWith(this.logArgs);
  119. });
  120. it('should log warn', function() {
  121. this.logger.warn(this.logArgs);
  122. this.mockBunyanLogger.warn.should.have.been.calledWith(this.logArgs);
  123. });
  124. it('should log err', function() {
  125. this.logger.err(this.logArgs);
  126. this.mockBunyanLogger.error.should.have.been.calledWith(this.logArgs);
  127. });
  128. it('should log log', function() {
  129. this.logger.log(this.logArgs);
  130. this.mockBunyanLogger.info.should.have.been.calledWith(this.logArgs);
  131. });
  132. });
  133. describe('logger.error', function() {
  134. it('should report a single error to sentry', function() {
  135. this.logger.error({foo:'bar'}, "message");
  136. this.captureException.called.should.equal(true);
  137. });
  138. it('should report the same error to sentry only once', function() {
  139. const error1 = new Error('this is the error');
  140. this.logger.error({foo: error1}, "first message");
  141. this.logger.error({bar: error1}, "second message");
  142. this.captureException.callCount.should.equal(1);
  143. });
  144. it('should report two different errors to sentry individually', function() {
  145. const error1 = new Error('this is the error');
  146. const error2 = new Error('this is the error');
  147. this.logger.error({foo: error1}, "first message");
  148. this.logger.error({bar: error2}, "second message");
  149. this.captureException.callCount.should.equal(2);
  150. });
  151. it('should remove the path from fs errors', function() {
  152. const fsError = new Error("Error: ENOENT: no such file or directory, stat '/tmp/3279b8d0-da10-11e8-8255-efd98985942b'");
  153. fsError.path = "/tmp/3279b8d0-da10-11e8-8255-efd98985942b";
  154. this.logger.error({err: fsError}, "message");
  155. this.captureException.calledWith(sinon.match.has('message', 'Error: ENOENT: no such file or directory, stat')).should.equal(true);
  156. });
  157. it('for multiple errors should only report a maximum of 5 errors to sentry', function() {
  158. this.logger.error({foo:'bar'}, "message");
  159. this.logger.error({foo:'bar'}, "message");
  160. this.logger.error({foo:'bar'}, "message");
  161. this.logger.error({foo:'bar'}, "message");
  162. this.logger.error({foo:'bar'}, "message");
  163. this.logger.error({foo:'bar'}, "message");
  164. this.logger.error({foo:'bar'}, "message");
  165. this.logger.error({foo:'bar'}, "message");
  166. this.logger.error({foo:'bar'}, "message");
  167. this.captureException.callCount.should.equal(5);
  168. });
  169. it('for multiple errors with a minute delay should report 10 errors to sentry', function() {
  170. // the first five errors should be reported to sentry
  171. this.logger.error({foo:'bar'}, "message");
  172. this.logger.error({foo:'bar'}, "message");
  173. this.logger.error({foo:'bar'}, "message");
  174. this.logger.error({foo:'bar'}, "message");
  175. this.logger.error({foo:'bar'}, "message");
  176. // the following errors should not be reported
  177. this.logger.error({foo:'bar'}, "message");
  178. this.logger.error({foo:'bar'}, "message");
  179. this.logger.error({foo:'bar'}, "message");
  180. this.logger.error({foo:'bar'}, "message");
  181. // allow a minute to pass
  182. this.clock.tick(this.start+ (61*1000));
  183. // after a minute the next five errors should be reported to sentry
  184. this.logger.error({foo:'bar'}, "message");
  185. this.logger.error({foo:'bar'}, "message");
  186. this.logger.error({foo:'bar'}, "message");
  187. this.logger.error({foo:'bar'}, "message");
  188. this.logger.error({foo:'bar'}, "message");
  189. // the following errors should not be reported to sentry
  190. this.logger.error({foo:'bar'}, "message");
  191. this.logger.error({foo:'bar'}, "message");
  192. this.logger.error({foo:'bar'}, "message");
  193. this.logger.error({foo:'bar'}, "message");
  194. this.captureException.callCount.should.equal(10);
  195. });
  196. });
  197. describe('checkLogLevel', function() {
  198. it('should request log level override from google meta data service', function() {
  199. this.logger.checkLogLevel();
  200. const options = {
  201. headers: {
  202. "Metadata-Flavor": "Google"
  203. },
  204. uri: `http://metadata.google.internal/computeMetadata/v1/project/attributes/${this.loggerName}-setLogLevelEndTime`
  205. };
  206. this.Request.should.have.been.calledWithMatch(options);
  207. });
  208. describe('when request has error', function() {
  209. beforeEach(function() {
  210. this.Request.yields("error");
  211. this.logger.checkLogLevel();
  212. });
  213. it("should only set default level", function() {
  214. this.mockBunyanLogger.level.should.have.been.calledOnce.and.calledWith('debug');
  215. });
  216. });
  217. describe('when statusCode is not 200', function() {
  218. beforeEach(function() {
  219. this.Request.yields(null, {statusCode: 404});
  220. this.logger.checkLogLevel();
  221. });
  222. it("should only set default level", function() {
  223. this.mockBunyanLogger.level.should.have.been.calledOnce.and.calledWith('debug');
  224. });
  225. });
  226. describe('when time value returned that is less than current time', function() {
  227. beforeEach(function() {
  228. this.Request.yields(null, {statusCode: 200}, '1');
  229. this.logger.checkLogLevel();
  230. });
  231. it("should only set default level", function() {
  232. this.mockBunyanLogger.level.should.have.been.calledOnce.and.calledWith('debug');
  233. });
  234. });
  235. describe('when time value returned that is less than current time', function() {
  236. describe('when level is already set', function() {
  237. beforeEach(function() {
  238. this.mockBunyanLogger.level.returns(10);
  239. this.Request.yields(null, {statusCode: 200}, this.start + 1000);
  240. this.logger.checkLogLevel();
  241. });
  242. it("should set trace level", function() {
  243. this.mockBunyanLogger.level.should.have.been.calledOnce.and.calledWith('trace');
  244. });
  245. });
  246. describe('when level is not already set', function() {
  247. beforeEach(function() {
  248. this.mockBunyanLogger.level.returns(20);
  249. this.Request.yields(null, {statusCode: 200}, this.start + 1000);
  250. this.logger.checkLogLevel();
  251. });
  252. it("should set trace level", function() {
  253. this.mockBunyanLogger.level.should.have.been.calledOnce.and.calledWith('trace');
  254. });
  255. });
  256. });
  257. });
  258. });