log-level-checker-tests.js 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. const Path = require('path')
  2. const { promisify } = require('util')
  3. const SandboxedModule = require('sandboxed-module')
  4. const sinon = require('sinon')
  5. const { expect } = require('chai')
  6. const MODULE_PATH = Path.join(__dirname, '../../log-level-checker.js')
  7. const DEFAULT_LEVEL = 'warn'
  8. const TRACE_LEVEL = 'trace'
  9. const TRACING_END_TIME_FILE = '/logging/tracingEndTime'
  10. const NOW = 10000
  11. const PAST = NOW - 1000
  12. const FUTURE = NOW + 1000
  13. const delay = promisify(setTimeout)
  14. describe('LogLevelChecker', function () {
  15. beforeEach(function () {
  16. this.logger = {
  17. level: sinon.stub(),
  18. fields: { name: 'myapp' },
  19. }
  20. this.fetchResponse = {
  21. text: sinon.stub().resolves(''),
  22. status: 200,
  23. ok: true,
  24. }
  25. this.fetch = sinon
  26. .stub()
  27. .withArgs(
  28. 'http://metadata.google.internal/computeMetadata/v1/project/attributes/myapp-setLogLevelEndTime',
  29. { headers: { 'Metadata-Flavor': 'Google' } }
  30. )
  31. .resolves(this.fetchResponse)
  32. this.fs = {
  33. promises: {
  34. readFile: sinon.stub(),
  35. },
  36. }
  37. this.clock = sinon.useFakeTimers(NOW)
  38. this.module = SandboxedModule.require(MODULE_PATH, {
  39. requires: {
  40. 'node-fetch': this.fetch,
  41. fs: this.fs,
  42. },
  43. })
  44. })
  45. afterEach(function () {
  46. this.clock.restore()
  47. })
  48. describe('FileLogLevelChecker', function () {
  49. beforeEach(function () {
  50. this.logLevelChecker = new this.module.FileLogLevelChecker(
  51. this.logger,
  52. DEFAULT_LEVEL
  53. )
  54. })
  55. describe('when the file is empty', function () {
  56. setupTracingEndTimeFile('')
  57. checkLogLevel()
  58. expectLevelSetTo(DEFAULT_LEVEL)
  59. })
  60. describe("when the file can't be read", function () {
  61. beforeEach(async function () {
  62. this.fs.promises.readFile.rejects(new Error('Read error!'))
  63. })
  64. checkLogLevel()
  65. expectLevelSetTo(DEFAULT_LEVEL)
  66. })
  67. describe('when the file has a timestamp in the future', function () {
  68. setupTracingEndTimeFile(FUTURE.toString())
  69. checkLogLevel()
  70. expectLevelSetTo(TRACE_LEVEL)
  71. })
  72. describe('when the file has a timestamp in the past', function () {
  73. setupTracingEndTimeFile(PAST.toString())
  74. checkLogLevel()
  75. expectLevelSetTo(DEFAULT_LEVEL)
  76. })
  77. describe('interval checker', function () {
  78. beforeEach(function () {
  79. this.fs.promises.readFile.resolves('')
  80. this.logLevelChecker.start()
  81. })
  82. afterEach(function () {
  83. this.logLevelChecker.stop()
  84. })
  85. it('checks the file every minute', async function () {
  86. this.clock.tick(1000)
  87. // Yield to the event loop
  88. await delay(0)
  89. expect(this.logger.level).to.have.been.calledOnceWithExactly(
  90. DEFAULT_LEVEL
  91. )
  92. this.logger.level.reset()
  93. // Trace until 1.5 minutes in the future
  94. const traceUntil = NOW + 90000
  95. this.fs.promises.readFile.resolves(traceUntil.toString())
  96. this.clock.tick(61000)
  97. await delay(0)
  98. expect(this.logger.level).to.have.been.calledOnceWithExactly(
  99. TRACE_LEVEL
  100. )
  101. this.logger.level.reset()
  102. this.clock.tick(60000)
  103. await delay(0)
  104. expect(this.logger.level).to.have.been.calledOnceWithExactly(
  105. DEFAULT_LEVEL
  106. )
  107. })
  108. })
  109. })
  110. describe('GCEMetadataLogLevelChecker', function () {
  111. beforeEach(function () {
  112. this.logLevelChecker = new this.module.GCEMetadataLogLevelChecker(
  113. this.logger,
  114. DEFAULT_LEVEL
  115. )
  116. })
  117. describe('when the response is empty', function () {
  118. setupTracingEndTimeGCE('')
  119. checkLogLevel()
  120. expectLevelSetTo(DEFAULT_LEVEL)
  121. })
  122. describe('when the request errors', function () {
  123. beforeEach(async function () {
  124. this.fetchResponse.text.rejects(new Error('Read error!'))
  125. })
  126. checkLogLevel()
  127. expectLevelSetTo(DEFAULT_LEVEL)
  128. })
  129. describe('when the request returns a failure status code', function () {
  130. beforeEach(async function () {
  131. this.fetchResponse.status = 500
  132. this.fetchResponse.ok = false
  133. })
  134. checkLogLevel()
  135. expectLevelSetTo(DEFAULT_LEVEL)
  136. })
  137. describe('when the response is a timestamp in the future', function () {
  138. setupTracingEndTimeGCE(FUTURE.toString())
  139. checkLogLevel()
  140. expectLevelSetTo(TRACE_LEVEL)
  141. })
  142. describe('when the response is a timestamp in the past', function () {
  143. setupTracingEndTimeGCE(PAST.toString())
  144. checkLogLevel()
  145. expectLevelSetTo(DEFAULT_LEVEL)
  146. })
  147. })
  148. })
  149. function setupTracingEndTimeFile(contents) {
  150. beforeEach(`set tracing end time in file to ${contents}`, function () {
  151. this.fs.promises.readFile.withArgs(TRACING_END_TIME_FILE).resolves(contents)
  152. })
  153. }
  154. function setupTracingEndTimeGCE(contents) {
  155. beforeEach(
  156. `set tracing end time in GCE metadata to ${contents}`,
  157. function () {
  158. this.fetchResponse.text.resolves(contents)
  159. }
  160. )
  161. }
  162. function checkLogLevel() {
  163. beforeEach('Check log level', async function () {
  164. await this.logLevelChecker.checkLogLevel()
  165. })
  166. }
  167. function expectLevelSetTo(level) {
  168. it(`sets the log level to ${level}`, function () {
  169. expect(this.logger.level).to.have.been.calledWith(level)
  170. })
  171. }