logging.js 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. /*---------------------------------------------------------------------------------------------
  2. * Copyright (c) Microsoft Corporation. All rights reserved.
  3. * Licensed under the MIT License. See License.txt in the project root for license information.
  4. *--------------------------------------------------------------------------------------------*/
  5. let globalObservableLogger;
  6. export function setLogger(logger) {
  7. globalObservableLogger = logger;
  8. }
  9. export function getLogger() {
  10. return globalObservableLogger;
  11. }
  12. export class ConsoleObservableLogger {
  13. constructor() {
  14. this.indentation = 0;
  15. this.changedObservablesSets = new WeakMap();
  16. }
  17. textToConsoleArgs(text) {
  18. return consoleTextToArgs([
  19. normalText(repeat('| ', this.indentation)),
  20. text,
  21. ]);
  22. }
  23. formatInfo(info) {
  24. return info.didChange
  25. ? [
  26. normalText(` `),
  27. styled(formatValue(info.oldValue, 70), {
  28. color: 'red',
  29. strikeThrough: true,
  30. }),
  31. normalText(` `),
  32. styled(formatValue(info.newValue, 60), {
  33. color: 'green',
  34. }),
  35. ]
  36. : [normalText(` (unchanged)`)];
  37. }
  38. handleObservableChanged(observable, info) {
  39. console.log(...this.textToConsoleArgs([
  40. formatKind('observable value changed'),
  41. styled(observable.debugName, { color: 'BlueViolet' }),
  42. ...this.formatInfo(info),
  43. ]));
  44. }
  45. formatChanges(changes) {
  46. if (changes.size === 0) {
  47. return undefined;
  48. }
  49. return styled(' (changed deps: ' +
  50. [...changes].map((o) => o.debugName).join(', ') +
  51. ')', { color: 'gray' });
  52. }
  53. handleDerivedCreated(derived) {
  54. const existingHandleChange = derived.handleChange;
  55. this.changedObservablesSets.set(derived, new Set());
  56. derived.handleChange = (observable, change) => {
  57. this.changedObservablesSets.get(derived).add(observable);
  58. return existingHandleChange.apply(derived, [observable, change]);
  59. };
  60. }
  61. handleDerivedRecomputed(derived, info) {
  62. const changedObservables = this.changedObservablesSets.get(derived);
  63. console.log(...this.textToConsoleArgs([
  64. formatKind('derived recomputed'),
  65. styled(derived.debugName, { color: 'BlueViolet' }),
  66. ...this.formatInfo(info),
  67. this.formatChanges(changedObservables)
  68. ]));
  69. changedObservables.clear();
  70. }
  71. handleFromEventObservableTriggered(observable, info) {
  72. console.log(...this.textToConsoleArgs([
  73. formatKind('observable from event triggered'),
  74. styled(observable.debugName, { color: 'BlueViolet' }),
  75. ...this.formatInfo(info),
  76. ]));
  77. }
  78. handleAutorunCreated(autorun) {
  79. const existingHandleChange = autorun.handleChange;
  80. this.changedObservablesSets.set(autorun, new Set());
  81. autorun.handleChange = (observable, change) => {
  82. this.changedObservablesSets.get(autorun).add(observable);
  83. return existingHandleChange.apply(autorun, [observable, change]);
  84. };
  85. }
  86. handleAutorunTriggered(autorun) {
  87. const changedObservables = this.changedObservablesSets.get(autorun);
  88. console.log(...this.textToConsoleArgs([
  89. formatKind('autorun'),
  90. styled(autorun.debugName, { color: 'BlueViolet' }),
  91. this.formatChanges(changedObservables)
  92. ]));
  93. changedObservables.clear();
  94. }
  95. handleBeginTransaction(transaction) {
  96. let transactionName = transaction.getDebugName();
  97. if (transactionName === undefined) {
  98. transactionName = '';
  99. }
  100. console.log(...this.textToConsoleArgs([
  101. formatKind('transaction'),
  102. styled(transactionName, { color: 'BlueViolet' }),
  103. ]));
  104. this.indentation++;
  105. }
  106. handleEndTransaction() {
  107. this.indentation--;
  108. }
  109. }
  110. function consoleTextToArgs(text) {
  111. const styles = new Array();
  112. const initial = {};
  113. const data = initial;
  114. let firstArg = '';
  115. function process(t) {
  116. if ('length' in t) {
  117. for (const item of t) {
  118. if (item) {
  119. process(item);
  120. }
  121. }
  122. }
  123. else if ('text' in t) {
  124. firstArg += `%c${t.text}`;
  125. styles.push(t.style);
  126. if (t.data) {
  127. Object.assign(data, t.data);
  128. }
  129. }
  130. else if ('data' in t) {
  131. Object.assign(data, t.data);
  132. }
  133. }
  134. process(text);
  135. const result = [firstArg, ...styles];
  136. if (Object.keys(data).length > 0) {
  137. result.push(data);
  138. }
  139. return result;
  140. }
  141. function normalText(text) {
  142. return styled(text, { color: 'black' });
  143. }
  144. function formatKind(kind) {
  145. return styled(padStr(`${kind}: `, 10), { color: 'black', bold: true });
  146. }
  147. function styled(text, options = {
  148. color: 'black',
  149. }) {
  150. function objToCss(styleObj) {
  151. return Object.entries(styleObj).reduce((styleString, [propName, propValue]) => {
  152. return `${styleString}${propName}:${propValue};`;
  153. }, '');
  154. }
  155. const style = {
  156. color: options.color,
  157. };
  158. if (options.strikeThrough) {
  159. style['text-decoration'] = 'line-through';
  160. }
  161. if (options.bold) {
  162. style['font-weight'] = 'bold';
  163. }
  164. return {
  165. text,
  166. style: objToCss(style),
  167. };
  168. }
  169. function formatValue(value, availableLen) {
  170. switch (typeof value) {
  171. case 'number':
  172. return '' + value;
  173. case 'string':
  174. if (value.length + 2 <= availableLen) {
  175. return `"${value}"`;
  176. }
  177. return `"${value.substr(0, availableLen - 7)}"+...`;
  178. case 'boolean':
  179. return value ? 'true' : 'false';
  180. case 'undefined':
  181. return 'undefined';
  182. case 'object':
  183. if (value === null) {
  184. return 'null';
  185. }
  186. if (Array.isArray(value)) {
  187. return formatArray(value, availableLen);
  188. }
  189. return formatObject(value, availableLen);
  190. case 'symbol':
  191. return value.toString();
  192. case 'function':
  193. return `[[Function${value.name ? ' ' + value.name : ''}]]`;
  194. default:
  195. return '' + value;
  196. }
  197. }
  198. function formatArray(value, availableLen) {
  199. let result = '[ ';
  200. let first = true;
  201. for (const val of value) {
  202. if (!first) {
  203. result += ', ';
  204. }
  205. if (result.length - 5 > availableLen) {
  206. result += '...';
  207. break;
  208. }
  209. first = false;
  210. result += `${formatValue(val, availableLen - result.length)}`;
  211. }
  212. result += ' ]';
  213. return result;
  214. }
  215. function formatObject(value, availableLen) {
  216. let result = '{ ';
  217. let first = true;
  218. for (const [key, val] of Object.entries(value)) {
  219. if (!first) {
  220. result += ', ';
  221. }
  222. if (result.length - 5 > availableLen) {
  223. result += '...';
  224. break;
  225. }
  226. first = false;
  227. result += `${key}: ${formatValue(val, availableLen - result.length)}`;
  228. }
  229. result += ' }';
  230. return result;
  231. }
  232. function repeat(str, count) {
  233. let result = '';
  234. for (let i = 1; i <= count; i++) {
  235. result += str;
  236. }
  237. return result;
  238. }
  239. function padStr(str, length) {
  240. while (str.length < length) {
  241. str += ' ';
  242. }
  243. return str;
  244. }