All files logger.js

100% Statements 112/112
98.11% Branches 52/53
100% Functions 15/15
100% Lines 112/112

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 1139x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 421x 421x 9x 9x 482x 481x 482x 9x 9x 9x 146x 289x 146x 146x 146x 9x 143x 143x 143x 143x 1x 1x 143x 9x 67x 67x 67x 67x 67x 66x 67x 9x 9x 9x 9x 9x 9x 9x 9x 353x 353x 353x 353x 9x 9x 1409x 1409x 9x 9x 546x 546x 546x 546x 9x 9x 1409x 546x 546x 1004x 1x 1x 1409x 9x 9x 18x 18x 9x 9x 107x 107x 9x 9x 119x 119x 9x 9x 1165x 1165x 9x 9x 9x 9x 9x 346x 346x 346x 9x 9x 1017x 1017x 19x 19x 19x 1017x 1x 1x 1017x 9x  
// Logging with levels and redaction (DESIGN 2.7). secretKey, signature, token and audio bytes are
// never logged; appKey is logged as its first 4 characters plus ***.
 
export const LogLevel = Object.freeze({
  OFF: 'OFF',
  ERROR: 'ERROR',
  WARN: 'WARN',
  INFO: 'INFO',
  DEBUG: 'DEBUG',
});
 
const RANK = { OFF: 0, ERROR: 1, WARN: 2, INFO: 3, DEBUG: 4 };
 
export function isLogLevel(v) {
  return typeof v === 'string' && Object.prototype.hasOwnProperty.call(RANK, v);
}
 
export function maskAppKey(appKey) {
  if (!appKey) return '';
  return String(appKey).slice(0, 4) + '***';
}
 
/** Redacts credential query parameters of a URL before it is logged. */
export function redactUrl(url) {
  return String(url).replace(/([?&])(signature|token|secretKey|appKey)=([^&#]*)/g, (all, sep, name, value) => {
    if (name === 'appKey') return sep + name + '=' + maskAppKey(decodeSafe(value));
    return sep + name + '=***';
  });
}
 
function decodeSafe(v) {
  try {
    return decodeURIComponent(v);
  } catch (e) {
    return v;
  }
}
 
function consoleSink(level, tag, message, error) {
  if (typeof console === 'undefined') return;
  const fn = level === 'ERROR' ? console.error : level === 'WARN' ? console.warn : level === 'INFO' ? console.info : console.log;
  const line = '[' + tag + '] ' + message;
  if (error !== undefined) fn.call(console, line, error);
  else fn.call(console, line);
}
 
export class Logger {
  /**
   * @param level   OFF, ERROR, WARN, INFO or DEBUG
   * @param sink    function (level, tag, message, error) or undefined for the console
   * @param secrets strings that must never appear in a log line (secretKey, token)
   */
  constructor(level, sink, secrets) {
    this.level = isLogLevel(level) ? level : LogLevel.WARN;
    this.sink = typeof sink === 'function' ? sink : consoleSink;
    this.secrets = (secrets || []).filter((s) => typeof s === 'string' && s.length >= 4);
  }
 
  enabled(level) {
    return RANK[level] > 0 && RANK[level] <= RANK[this.level];
  }
 
  scrub(text) {
    let s = String(text);
    for (let i = 0; i < this.secrets.length; i++) s = s.split(this.secrets[i]).join('***');
    return s;
  }
 
  log(level, tag, message, error) {
    if (!this.enabled(level)) return;
    try {
      this.sink(level, tag, this.scrub(message), error);
    } catch (e) {
      // A failing sink must never break the SDK.
    }
  }
 
  error(tag, message, error) {
    this.log('ERROR', tag, message, error);
  }
 
  warn(tag, message, error) {
    this.log('WARN', tag, message, error);
  }
 
  info(tag, message, error) {
    this.log('INFO', tag, message, error);
  }
 
  debug(tag, message, error) {
    this.log('DEBUG', tag, message, error);
  }
}
 
/** Calls optional EventListener hooks (DESIGN 2.7), isolating the SDK from exceptions they throw. */
export class EventHub {
  constructor(listener, logger) {
    this.listener = listener && typeof listener === 'object' ? listener : null;
    this.logger = logger;
  }
 
  emit(name) {
    const l = this.listener;
    if (!l || typeof l[name] !== 'function') return;
    const args = Array.prototype.slice.call(arguments, 1);
    try {
      l[name].apply(l, args);
    } catch (e) {
      if (this.logger) this.logger.error('YuguSDK', 'eventListener.' + name + ' threw', e);
    }
  }
}