github.com/hernad/nomad@v1.6.112/ui/app/utils/classes/log.js (about)

     1  /**
     2   * Copyright (c) HashiCorp, Inc.
     3   * SPDX-License-Identifier: MPL-2.0
     4   */
     5  
     6  import { alias } from '@ember/object/computed';
     7  import { assert } from '@ember/debug';
     8  import { htmlSafe } from '@ember/template';
     9  import Evented from '@ember/object/evented';
    10  import EmberObject, { computed } from '@ember/object';
    11  import { computed as overridable } from 'ember-overridable-computed';
    12  import { assign } from '@ember/polyfills';
    13  import queryString from 'query-string';
    14  import { task } from 'ember-concurrency';
    15  import StreamLogger from 'nomad-ui/utils/classes/stream-logger';
    16  import PollLogger from 'nomad-ui/utils/classes/poll-logger';
    17  import { decode } from 'nomad-ui/utils/stream-frames';
    18  import Anser from 'anser';
    19  import classic from 'ember-classic-decorator';
    20  
    21  const MAX_OUTPUT_LENGTH = 50000;
    22  
    23  // eslint-disable-next-line
    24  export const fetchFailure = (url) => () =>
    25    console.warn(`LOG FETCH: Couldn't connect to ${url}`);
    26  
    27  @classic
    28  class Log extends EmberObject.extend(Evented) {
    29    // Parameters
    30  
    31    url = '';
    32  
    33    @overridable(() => ({}))
    34    params;
    35  
    36    plainText = false;
    37  
    38    logFetch() {
    39      assert(
    40        'Log objects need a logFetch method, which should have an interface like window.fetch'
    41      );
    42    }
    43  
    44    // Read-only state
    45  
    46    @alias('logStreamer.poll.isRunning')
    47    isStreaming;
    48  
    49    logPointer = null;
    50    logStreamer = null;
    51  
    52    // The top of the log
    53    head = '';
    54  
    55    // The bottom of the log
    56    tail = '';
    57  
    58    // The top or bottom of the log, depending on whether
    59    // the logPointer is pointed at head or tail
    60    @computed('logPointer', 'head', 'tail')
    61    get output() {
    62      let logs = this.logPointer === 'head' ? this.head : this.tail;
    63      logs = logs.replace(/</g, '&lt;').replace(/>/g, '&gt;');
    64      let colouredLogs = Anser.ansiToHtml(logs);
    65      return htmlSafe(colouredLogs);
    66    }
    67  
    68    init() {
    69      super.init();
    70  
    71      const args = this.getProperties('url', 'params', 'logFetch');
    72      args.write = (chunk) => {
    73        let newTail = this.tail + chunk;
    74        if (newTail.length > MAX_OUTPUT_LENGTH) {
    75          newTail = newTail.substr(newTail.length - MAX_OUTPUT_LENGTH);
    76        }
    77        this.set('tail', newTail);
    78        this.trigger('tick', chunk);
    79      };
    80  
    81      if (StreamLogger.isSupported) {
    82        this.set('logStreamer', StreamLogger.create(args));
    83      } else {
    84        this.set('logStreamer', PollLogger.create(args));
    85      }
    86    }
    87  
    88    destroy() {
    89      this.stop();
    90      super.destroy();
    91    }
    92  
    93    @task(function* () {
    94      const logFetch = this.logFetch;
    95      const queryParams = queryString.stringify(
    96        assign(
    97          {
    98            origin: 'start',
    99            offset: 0,
   100          },
   101          this.params
   102        )
   103      );
   104      const url = `${this.url}?${queryParams}`;
   105  
   106      this.stop();
   107      const response = yield logFetch(url).then(
   108        (res) => res.text(),
   109        fetchFailure(url)
   110      );
   111      let text = this.plainText ? response : decode(response).message;
   112  
   113      if (text && text.length > MAX_OUTPUT_LENGTH) {
   114        text = text.substr(0, MAX_OUTPUT_LENGTH);
   115        text +=
   116          '\n\n---------- TRUNCATED: Click "tail" to view the bottom of the log ----------';
   117      }
   118      this.set('head', text);
   119      this.set('logPointer', 'head');
   120    })
   121    gotoHead;
   122  
   123    @task(function* () {
   124      const logFetch = this.logFetch;
   125      const queryParams = queryString.stringify(
   126        assign(
   127          {
   128            origin: 'end',
   129            offset: MAX_OUTPUT_LENGTH,
   130          },
   131          this.params
   132        )
   133      );
   134      const url = `${this.url}?${queryParams}`;
   135  
   136      this.stop();
   137      const response = yield logFetch(url).then(
   138        (res) => res.text(),
   139        fetchFailure(url)
   140      );
   141      let text = this.plainText ? response : decode(response).message;
   142  
   143      this.set('tail', text);
   144      this.set('logPointer', 'tail');
   145    })
   146    gotoTail;
   147  
   148    startStreaming() {
   149      this.set('logPointer', 'tail');
   150      return this.logStreamer.start();
   151    }
   152  
   153    stop() {
   154      this.logStreamer.stop();
   155    }
   156  }
   157  
   158  export default Log;
   159  
   160  export function logger(urlProp, params, logFetch) {
   161    return computed(urlProp, params, function () {
   162      return Log.create({
   163        logFetch: logFetch.call(this),
   164        params: this.get(params),
   165        url: this.get(urlProp),
   166      });
   167    });
   168  }