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 400x 400x 9x 9x 449x 448x 449x 9x 9x 9x 124x 245x 124x 124x 124x 9x 121x 121x 121x 121x 1x 1x 121x 9x 67x 67x 67x 67x 67x 66x 67x 9x 9x 9x 9x 9x 9x 9x 9x 342x 342x 342x 342x 9x 9x 1314x 1314x 9x 9x 455x 455x 455x 455x 9x 9x 1314x 455x 455x 889x 1x 1x 1314x 9x 9x 18x 18x 9x 9x 102x 102x 9x 9x 114x 114x 9x 9x 1080x 1080x 9x 9x 9x 9x 9x 335x 335x 335x 9x 9x 898x 898x 19x 19x 19x 898x 1x 1x 898x 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);
    }
  }
}