log-level-checker-tests.js 5.0 KB

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