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, '<').replace(/>/g, '>'); 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 }