github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/ui/app/utils/classes/stream-logger.js (about)

     1  import EmberObject, { computed } from '@ember/object';
     2  import { task } from 'ember-concurrency';
     3  import TextDecoder from 'nomad-ui/utils/classes/text-decoder';
     4  import { decode } from 'nomad-ui/utils/stream-frames';
     5  import AbstractLogger from './abstract-logger';
     6  import { fetchFailure } from './log';
     7  import classic from 'ember-classic-decorator';
     8  
     9  @classic
    10  export default class StreamLogger extends EmberObject.extend(AbstractLogger) {
    11    reader = null;
    12  
    13    @computed()
    14    get additionalParams() {
    15      return {
    16        follow: true,
    17      };
    18    }
    19  
    20    start() {
    21      return this.poll.perform();
    22    }
    23  
    24    stop() {
    25      const reader = this.reader;
    26      if (reader) {
    27        reader.cancel();
    28      }
    29      return this.poll.cancelAll();
    30    }
    31  
    32    @task(function*() {
    33      const url = this.fullUrl;
    34      const logFetch = this.logFetch;
    35  
    36      const reader = yield logFetch(url).then(res => {
    37        const reader = res.body.getReader();
    38        // It's possible that the logger was stopped between the time
    39        // polling was started and the log request responded.
    40        // If the logger was stopped, the reader needs to be immediately
    41        // canceled to prevent an endless request running in the background.
    42        if (this.poll.isRunning) {
    43          return reader;
    44        }
    45        reader.cancel();
    46      }, fetchFailure(url));
    47  
    48      if (!reader) {
    49        return;
    50      }
    51  
    52      this.set('reader', reader);
    53  
    54      let streamClosed = false;
    55      let buffer = '';
    56      const decoder = new TextDecoder();
    57  
    58      while (!streamClosed) {
    59        yield reader.read().then(({ value, done }) => {
    60          streamClosed = done;
    61  
    62          // There is no guarantee that value will be a complete JSON object,
    63          // so it needs to be buffered.
    64          buffer += decoder.decode(value, { stream: true });
    65  
    66          // Only when the buffer contains a close bracket can we be sure the buffer
    67          // is in a complete state
    68          if (buffer.indexOf('}') !== -1) {
    69            // The buffer can be one or more complete frames with additional text for the
    70            // next frame
    71            const [, chunk, newBuffer] = buffer.match(/(.*\})(.*)$/);
    72  
    73            // Peel chunk off the front of the buffer (since it represents complete frames)
    74            // and set the buffer to be the remainder
    75            buffer = newBuffer;
    76  
    77            // Assuming the logs endpoint never returns nested JSON (it shouldn't), at this
    78            // point chunk is a series of valid JSON objects with no delimiter.
    79            const { offset, message } = decode(chunk);
    80            if (message) {
    81              this.set('endOffset', offset);
    82              this.write(message);
    83            }
    84          }
    85        });
    86      }
    87    })
    88    poll;
    89  }
    90  
    91  StreamLogger.reopenClass({
    92    isSupported: !!window.ReadableStream,
    93  });