/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; import * as loc from '../localizedConstants'; export enum LogLevel { 'Pii', 'Off', 'Critical', 'Error', 'Warning', 'Information', 'Verbose', 'All', } export class Logger { public static channel: vscode.OutputChannel = vscode.window.createOutputChannel(loc.extensionName); private static _piiLogging: boolean = false; public static config = vscode.workspace.getConfiguration('azure'); public static shouldLog(logLevel: LogLevel): Boolean { return logLevel <= LogLevel[Logger.config.loggingLevel as keyof typeof LogLevel]; } private static write(logLevel: LogLevel, msg: any, ...vals: any[]) { if (this.shouldLog(logLevel) || logLevel === LogLevel.Pii) { const fullMessage = `[${LogLevel[logLevel]}]: ${msg} - ${vals.map(v => JSON.stringify(v)).join(' - ')}`; this.channel.appendLine(fullMessage); } } public static error(msg: any, ...vals: any[]) { this.write(LogLevel.Error, msg, vals); } public static info(msg: any, ...vals: any[]) { this.write(LogLevel.Information, msg, vals); } public static verbose(msg: any, ...vals: any[]) { this.write(LogLevel.Verbose, msg, vals); } /** * Logs a message containing PII (when enabled). Provides the ability to sanitize or shorten values to hide information or reduce the amount logged. * @param msg The initial message to log * @param objsToSanitize Set of objects we want to sanitize * @param stringsToShorten Set of strings to shorten * @param vals Any other values to add on to the end of the log message */ static pii(msg: any, objsToSanitize: { name: string, objOrArray: any | any[] }[], stringsToShorten: { name: string, value: string }[], ...vals: any[]) { if (this.piiLogging) { msg = [ msg, ...objsToSanitize.map(obj => `${obj.name}=${sanitize(obj.objOrArray)}`), ...stringsToShorten.map(str => `${str.name}=${shorten(str.value)}`) ].join(' '); Logger.write(LogLevel.Pii, msg, vals); } } public static set piiLogging(val: boolean) { this._piiLogging = val; } public static get piiLogging(): boolean { return this._piiLogging; } } /** * Sanitizes a given object for logging to the output window, removing/shortening any PII or unneeded values * @param objOrArray The object to sanitize for output logging * @returns The stringified version of the sanitized object */ function sanitize(objOrArray: any): string { if (Array.isArray(objOrArray)) { return JSON.stringify(objOrArray.map(o => sanitizeImpl(o))); } else { return sanitizeImpl(objOrArray); } } function sanitizeImpl(obj: any): string { obj = Object.assign({}, obj); delete obj.domains; // very long and not really useful // shorten all tokens since we don't usually need the exact values and there's security concerns if they leaked shortenIfExists(obj, 'token'); shortenIfExists(obj, 'refresh_token'); shortenIfExists(obj, 'access_token'); shortenIfExists(obj, 'code'); shortenIfExists(obj, 'id_token'); return JSON.stringify(obj); } /** * Shortens the given string property on an object if it exists, otherwise does nothing * @param obj The object possibly containing the property * @param property The name of the property to shorten - if it exists */ function shortenIfExists(obj: any, property: string): void { if (obj[property]) { obj[property] = shorten(obj[property]); } } /** * Shortens a given string - if it's longer than 6 characters will return the first 3 characters * followed by a ... followed by the last 3 characters. Returns the original string if 6 characters * or less. * @param str The string to shorten * @returns Shortened string in the form 'xxx...xxx' */ function shorten(str?: string): string | undefined { // Don't shorten if adding the ... wouldn't make the string shorter if (!str || str.length < 10) { return str; } return `${str.substr(0, 3)}...${str.slice(-3)}`; }