diff --git a/packages/zowe-explorer-api/CHANGELOG.md b/packages/zowe-explorer-api/CHANGELOG.md index 486b3d0d6f..ed9bc41a4b 100644 --- a/packages/zowe-explorer-api/CHANGELOG.md +++ b/packages/zowe-explorer-api/CHANGELOG.md @@ -9,6 +9,7 @@ All notable changes to the "zowe-explorer-api" extension will be documented in t - Added `Gui.reportProgress` that can be used to notify users of action progress in conjunction with the `Gui.withProgress` call. [#2167](https://github.com/zowe/vscode-extension-for-zowe/issues/2167) - Updated linter rules and addressed linter errors throughout the codebase. [#2184](https://github.com/zowe/vscode-extension-for-zowe/issues/2184) - Added checks to verify that `@zowe/cli` dependency exists before building. [#2199](https://github.com/zowe/vscode-extension-for-zowe/issues/2199) +- Added `ZoweVsCodeExtension.customLoggingPath` that can be used to get custom logging path defined in VS Code settings. [#2186](https://github.com/zowe/vscode-extension-for-zowe/issues/2186) ### Bug fixes diff --git a/packages/zowe-explorer-api/__tests__/__unit__/vscode/ZoweVsCodeExtension.unit.test.ts b/packages/zowe-explorer-api/__tests__/__unit__/vscode/ZoweVsCodeExtension.unit.test.ts index a922f5cf5c..ef83041647 100644 --- a/packages/zowe-explorer-api/__tests__/__unit__/vscode/ZoweVsCodeExtension.unit.test.ts +++ b/packages/zowe-explorer-api/__tests__/__unit__/vscode/ZoweVsCodeExtension.unit.test.ts @@ -26,6 +26,16 @@ describe("ZoweVsCodeExtension", () => { jest.clearAllMocks(); }); + it("customLoggingPath should return value if defined in VS Code settings", () => { + const mockGetConfig = jest.fn().mockReturnValueOnce(__dirname); + jest.spyOn(vscode.workspace, "getConfiguration").mockReturnValue({ + get: mockGetConfig, + } as unknown as vscode.WorkspaceConfiguration); + expect(ZoweVsCodeExtension.customLoggingPath).toBe(__dirname); + expect(ZoweVsCodeExtension.customLoggingPath).toBeUndefined(); + expect(mockGetConfig).toHaveBeenCalledTimes(2); + }); + describe("getZoweExplorerApi", () => { it("should return client API", () => { jest.spyOn(vscode.extensions, "getExtension").mockReturnValueOnce(fakeVsce); diff --git a/packages/zowe-explorer-api/src/vscode/ZoweVsCodeExtension.ts b/packages/zowe-explorer-api/src/vscode/ZoweVsCodeExtension.ts index fc554c13a3..5436951cfd 100644 --- a/packages/zowe-explorer-api/src/vscode/ZoweVsCodeExtension.ts +++ b/packages/zowe-explorer-api/src/vscode/ZoweVsCodeExtension.ts @@ -25,6 +25,13 @@ export class ZoweVsCodeExtension { return new ProfilesCache(imperative.Logger.getAppLogger(), vscode.workspace.workspaceFolders?.[0]?.uri.fsPath); } + /** + * Get custom logging path if one is defined in VS Code settings. + */ + public static get customLoggingPath(): string | undefined { + return vscode.workspace.getConfiguration("zowe").get("files.logsFolder.path") || undefined; + } + /** * @param {string} [requiredVersion] Optional semver string specifying the minimal required version * of Zowe Explorer that needs to be installed for the API to be usable to the client. diff --git a/packages/zowe-explorer-ftp-extension/CHANGELOG.md b/packages/zowe-explorer-ftp-extension/CHANGELOG.md index c42f43334e..6cd57d8aa1 100644 --- a/packages/zowe-explorer-ftp-extension/CHANGELOG.md +++ b/packages/zowe-explorer-ftp-extension/CHANGELOG.md @@ -5,6 +5,7 @@ All notable changes to the "vscode-extension-for-zowe" extension will be documen ### New features and enhancements - Updated linter rules and addressed linter errors throughout the codebase. [#2184](https://github.com/zowe/vscode-extension-for-zowe/issues/2184) +- Added support for new setting `zowe.files.logsFolder.path` that can be used to override Zowe Explorer logs folder. [#2186](https://github.com/zowe/vscode-extension-for-zowe/issues/2186) ### Bug fixes diff --git a/packages/zowe-explorer-ftp-extension/__mocks__/@zowe/zowe-explorer-api.ts b/packages/zowe-explorer-ftp-extension/__mocks__/@zowe/zowe-explorer-api.ts index 3d98782420..47bae5ee27 100644 --- a/packages/zowe-explorer-ftp-extension/__mocks__/@zowe/zowe-explorer-api.ts +++ b/packages/zowe-explorer-ftp-extension/__mocks__/@zowe/zowe-explorer-api.ts @@ -52,8 +52,12 @@ export namespace Gui { } } -export namespace ZoweVsCodeExtension { - export function getZoweExplorerApi(requiredVersion?: string): any { +export class ZoweVsCodeExtension { + public static get customLoggingPath(): string | undefined { + return undefined; + } + + public static getZoweExplorerApi(requiredVersion?: string): any { return { registerUssApi: () => {}, registerJesApi: () => {}, diff --git a/packages/zowe-explorer-ftp-extension/src/extension.ts b/packages/zowe-explorer-ftp-extension/src/extension.ts index 25a75f89e8..abdab24b6f 100644 --- a/packages/zowe-explorer-ftp-extension/src/extension.ts +++ b/packages/zowe-explorer-ftp-extension/src/extension.ts @@ -19,7 +19,7 @@ import { CoreUtils } from "@zowe/zos-ftp-for-zowe-cli"; import { imperative } from "@zowe/cli"; import { FtpSession } from "./ftpSession"; -export const ZoweLogger = new IZoweLogger("Zowe Explorer FTP Extension", path.join(__dirname, "..", "..")); +export const ZoweLogger = new IZoweLogger("Zowe Explorer FTP Extension", ZoweVsCodeExtension.customLoggingPath ?? path.join(__dirname, "..", "..")); export function activate(_context: vscode.ExtensionContext): void { void registerFtpApis(); diff --git a/packages/zowe-explorer/CHANGELOG.md b/packages/zowe-explorer/CHANGELOG.md index ac6002a20d..9ed8646f82 100644 --- a/packages/zowe-explorer/CHANGELOG.md +++ b/packages/zowe-explorer/CHANGELOG.md @@ -10,6 +10,7 @@ All notable changes to the "vscode-extension-for-zowe" extension will be documen - Added an output channel, `Zowe Explorer`, for logging within VS Code's Output view. The log level is set by the new Zowe Explorer setting, `zowe.logger`. - Opening a dialog for Upload or Download of files will now open at the project level directory or the user's home directory if no project is opened. [#2203](https://github.com/zowe/vscode-extension-for-zowe/issues/2203) - Updated linter rules and addressed linter errors throughout the codebase. [#2184](https://github.com/zowe/vscode-extension-for-zowe/issues/2184) +- Added a new setting `zowe.files.logsFolder.path` that can be used to override Zowe Explorer logs folder if default location is read-only. [#2186](https://github.com/zowe/vscode-extension-for-zowe/issues/2186) ### Bug fixes diff --git a/packages/zowe-explorer/__tests__/__unit__/shared/init.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/shared/init.unit.test.ts index ce549b4159..d7cc8a2f47 100644 --- a/packages/zowe-explorer/__tests__/__unit__/shared/init.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/shared/init.unit.test.ts @@ -54,6 +54,18 @@ describe("Test src/shared/extension", () => { { name: "onDidChangeConfiguration:1", mock: [ + { spy: jest.spyOn(test.value, "affectsConfiguration"), arg: [globals.SETTINGS_LOGS_FOLDER_PATH], ret: true }, + { spy: jest.spyOn(globals, "initLogger"), arg: [test.value] }, + { spy: jest.spyOn(test.value, "affectsConfiguration"), arg: [globals.SETTINGS_TEMP_FOLDER_PATH], ret: false }, + { spy: jest.spyOn(test.value, "affectsConfiguration"), arg: [globals.SETTINGS_AUTOMATIC_PROFILE_VALIDATION], ret: false }, + { spy: jest.spyOn(test.value, "affectsConfiguration"), arg: [globals.SETTINGS_TEMP_FOLDER_HIDE], ret: false }, + { spy: jest.spyOn(test.value, "affectsConfiguration"), arg: [globals.SETTINGS_SECURE_CREDENTIALS_ENABLED], ret: false }, + ], + }, + { + name: "onDidChangeConfiguration:2", + mock: [ + { spy: jest.spyOn(test.value, "affectsConfiguration"), arg: [globals.SETTINGS_LOGS_FOLDER_PATH], ret: false }, { spy: jest.spyOn(test.value, "affectsConfiguration"), arg: [globals.SETTINGS_TEMP_FOLDER_PATH], ret: true }, { spy: jest.spyOn(tempFolder, "moveTempFolder"), arg: [undefined, test.value] }, { spy: jest.spyOn(test.value, "affectsConfiguration"), arg: [globals.SETTINGS_AUTOMATIC_PROFILE_VALIDATION], ret: false }, @@ -62,8 +74,9 @@ describe("Test src/shared/extension", () => { ], }, { - name: "onDidChangeConfiguration:2", + name: "onDidChangeConfiguration:3", mock: [ + { spy: jest.spyOn(test.value, "affectsConfiguration"), arg: [globals.SETTINGS_LOGS_FOLDER_PATH], ret: false }, { spy: jest.spyOn(test.value, "affectsConfiguration"), arg: [globals.SETTINGS_TEMP_FOLDER_PATH], ret: false }, { spy: jest.spyOn(test.value, "affectsConfiguration"), arg: [globals.SETTINGS_AUTOMATIC_PROFILE_VALIDATION], ret: true }, { spy: jest.spyOn(Profiles, "getInstance"), arg: [], ret: { refresh: jest.fn() } }, @@ -75,8 +88,9 @@ describe("Test src/shared/extension", () => { ], }, { - name: "onDidChangeConfiguration:3", + name: "onDidChangeConfiguration:4", mock: [ + { spy: jest.spyOn(test.value, "affectsConfiguration"), arg: [globals.SETTINGS_LOGS_FOLDER_PATH], ret: false }, { spy: jest.spyOn(test.value, "affectsConfiguration"), arg: [globals.SETTINGS_TEMP_FOLDER_PATH], ret: false }, { spy: jest.spyOn(test.value, "affectsConfiguration"), arg: [globals.SETTINGS_AUTOMATIC_PROFILE_VALIDATION], ret: false }, { spy: jest.spyOn(test.value, "affectsConfiguration"), arg: [globals.SETTINGS_TEMP_FOLDER_HIDE], ret: true }, @@ -85,8 +99,9 @@ describe("Test src/shared/extension", () => { ], }, { - name: "onDidChangeConfiguration:4", + name: "onDidChangeConfiguration:5", mock: [ + { spy: jest.spyOn(test.value, "affectsConfiguration"), arg: [globals.SETTINGS_LOGS_FOLDER_PATH], ret: false }, { spy: jest.spyOn(test.value, "affectsConfiguration"), arg: [globals.SETTINGS_TEMP_FOLDER_PATH], ret: false }, { spy: jest.spyOn(test.value, "affectsConfiguration"), arg: [globals.SETTINGS_AUTOMATIC_PROFILE_VALIDATION], ret: false }, { spy: jest.spyOn(test.value, "affectsConfiguration"), arg: [globals.SETTINGS_TEMP_FOLDER_HIDE], ret: false }, diff --git a/packages/zowe-explorer/__tests__/__unit__/uss/actions.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/uss/actions.unit.test.ts index 1b1785ce24..e6f4b28b91 100644 --- a/packages/zowe-explorer/__tests__/__unit__/uss/actions.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/uss/actions.unit.test.ts @@ -12,7 +12,7 @@ jest.mock("fs"); import * as zowe from "@zowe/cli"; -import { Gui, ProfilesCache, ValidProfileEnum } from "@zowe/zowe-explorer-api"; +import { Gui, ValidProfileEnum } from "@zowe/zowe-explorer-api"; import * as ussNodeActions from "../../../src/uss/actions"; import { UssFileTree, UssFileType, UssFileUtils } from "../../../src/uss/FileStructure"; import { createUSSTree, createUSSNode, createFavoriteUSSNode } from "../../../__mocks__/mockCreators/uss"; @@ -23,7 +23,6 @@ import { createTextDocument, createFileResponse, createValidIProfile, - createInstanceOfProfileInfo, } from "../../../__mocks__/mockCreators/shared"; import { ZoweExplorerApiRegister } from "../../../src/ZoweExplorerApiRegister"; import { Profiles } from "../../../src/Profiles"; @@ -75,23 +74,11 @@ function createGlobalMocks() { Notification: 15, }; }), - mockProfileInfo: createInstanceOfProfileInfo(), - mockProfilesCache: new ProfilesCache(zowe.imperative.Logger.getAppLogger()), }; globalMocks.mockLoadNamedProfile.mockReturnValue(globalMocks.testProfile); - // Mock the logger - globals.defineGlobals("/test/path/"); - const extensionMock = jest.fn( - () => - ({ - subscriptions: [], - extensionPath: path.join(__dirname, "..", ".."), - } as vscode.ExtensionContext) - ); - const mock = new extensionMock(); + globals.defineGlobals(""); const profilesForValidation = { status: "active", name: "fake" }; - globals.initLogger(mock); Object.defineProperty(Gui, "setStatusBarMessage", { value: globalMocks.setStatusBarMessage, configurable: true }); Object.defineProperty(vscode.window, "showInputBox", { value: globalMocks.mockShowInputBox, configurable: true }); @@ -169,11 +156,6 @@ function createGlobalMocks() { }; }), }); - Object.defineProperty(globalMocks.mockProfilesCache, "getProfileInfo", { - value: jest.fn(() => { - return { value: globalMocks.mockProfileInfo, configurable: true }; - }), - }); Object.defineProperty(ZoweLogger, "error", { value: jest.fn(), configurable: true }); Object.defineProperty(ZoweLogger, "debug", { value: jest.fn(), configurable: true }); Object.defineProperty(ZoweLogger, "warn", { value: jest.fn(), configurable: true }); diff --git a/packages/zowe-explorer/__tests__/__unit__/utils/LoggerUtils.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/utils/LoggerUtils.unit.test.ts index df61e9a8b6..cc2a2b0f04 100644 --- a/packages/zowe-explorer/__tests__/__unit__/utils/LoggerUtils.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/utils/LoggerUtils.unit.test.ts @@ -9,6 +9,7 @@ * */ +import * as path from "path"; import * as logger from "../../../src/utils/LoggerUtils"; import * as vscode from "vscode"; import * as zowe from "@zowe/cli"; @@ -22,6 +23,7 @@ function createGlobalMocks() { mockMessage: "fake message", outputChannel: shared.createOutputChannel(), mockGetConfiguration: jest.fn(), + mockLogger: jest.fn(), testContext: {} as unknown as vscode.ExtensionContext, }; newMocks.testContext = { @@ -40,7 +42,7 @@ function createGlobalMocks() { configurable: true, }); Object.defineProperty(Gui, "infoMessage", { value: jest.fn(), configurable: true }); - Object.defineProperty(globals, "LOG", { value: jest.fn(), configurable: true }); + Object.defineProperty(globals, "LOG", { value: newMocks.mockLogger, configurable: true }); Object.defineProperty(globals.LOG, "trace", { value: jest.fn(), configurable: true }); Object.defineProperty(globals.LOG, "debug", { value: jest.fn(), configurable: true }); Object.defineProperty(globals.LOG, "info", { value: jest.fn(), configurable: true }); @@ -61,6 +63,19 @@ function createGlobalMocks() { describe("Logger Utils Unit Tests - function initializeZoweLogger", () => { const env = process.env; + const getSettingMock = jest.fn((section: string) => { + switch (section) { + case "cliLoggerSetting.presented": + return false; + case "files.logsFolder.path": + return ""; + case "logger": + return "INFO"; + default: + throw new Error("Unknown Configuration Setting"); + } + }); + beforeEach(() => { jest.resetModules(); process.env = { ...env }; @@ -71,19 +86,11 @@ describe("Logger Utils Unit Tests - function initializeZoweLogger", () => { }); it("should initialize loggers successfully with no cli logger setting", async () => { const globalMocks = createGlobalMocks(); - jest.spyOn(globals, "initLogger").mockImplementationOnce(() => { - return "/fake/file/path"; - }); - globalMocks.mockGetConfiguration.mockReturnValueOnce({ - get: (setting: string) => "INFO", - update: jest.fn(), + jest.spyOn(globals, "initLogger").mockReturnValueOnce(); + globalMocks.mockGetConfiguration.mockReturnValue({ + get: getSettingMock, }); const infoSpy = jest.spyOn(logger.ZoweLogger, "info"); - process.env.ZOWE_APP_LOG_LEVEL = undefined; - globalMocks.mockGetConfiguration.mockReturnValueOnce({ - get: (setting: string) => "true", - update: jest.fn(), - }); expect(await logger.ZoweLogger.initializeZoweLogger(globalMocks.testContext)).toBeUndefined(); expect(infoSpy).toHaveBeenCalled(); @@ -91,23 +98,12 @@ describe("Logger Utils Unit Tests - function initializeZoweLogger", () => { }); it("should initialize loggers successfully with not changing to cli logger setting", async () => { const globalMocks = createGlobalMocks(); - jest.spyOn(globals, "initLogger").mockImplementationOnce(() => { - return "/fake/file/path"; - }); - globalMocks.mockGetConfiguration.mockReturnValueOnce({ - get: (setting: string) => "INFO", - update: jest.fn(), + jest.spyOn(globals, "initLogger").mockReturnValueOnce(); + globalMocks.mockGetConfiguration.mockReturnValue({ + get: getSettingMock, }); const infoSpy = jest.spyOn(logger.ZoweLogger, "info"); process.env.ZOWE_APP_LOG_LEVEL = "DEBUG"; - globalMocks.mockGetConfiguration.mockReturnValueOnce({ - get: (setting: string) => "INFO", - update: jest.fn(), - }); - globalMocks.mockGetConfiguration.mockReturnValueOnce({ - get: (setting: string) => false, - update: jest.fn(), - }); const messageSpy = jest.spyOn(Gui, "infoMessage").mockResolvedValueOnce(undefined); const updateSpy = jest.spyOn(SettingsConfig, "setDirectValue"); @@ -122,23 +118,12 @@ describe("Logger Utils Unit Tests - function initializeZoweLogger", () => { }); it("should initialize loggers successfully with changing to cli logger setting", async () => { const globalMocks = createGlobalMocks(); - jest.spyOn(globals, "initLogger").mockImplementationOnce(() => { - return "/fake/file/path"; - }); - globalMocks.mockGetConfiguration.mockReturnValueOnce({ - get: (setting: string) => "INFO", - update: jest.fn(), + jest.spyOn(globals, "initLogger").mockReturnValueOnce(); + globalMocks.mockGetConfiguration.mockReturnValue({ + get: getSettingMock, }); const infoSpy = jest.spyOn(logger.ZoweLogger, "info"); process.env.ZOWE_APP_LOG_LEVEL = "DEBUG"; - globalMocks.mockGetConfiguration.mockReturnValueOnce({ - get: (setting: string) => "INFO", - update: jest.fn(), - }); - globalMocks.mockGetConfiguration.mockReturnValueOnce({ - get: (setting: string) => false, - update: jest.fn(), - }); const messageSpy = jest.spyOn(Gui, "infoMessage").mockResolvedValueOnce("Update"); const updateSpy = jest.spyOn(SettingsConfig, "setDirectValue"); @@ -150,23 +135,50 @@ describe("Logger Utils Unit Tests - function initializeZoweLogger", () => { messageSpy.mockClear(); updateSpy.mockClear(); }); + it("should reinitialize loggers successfully with new path", async () => { + const globalMocks = createGlobalMocks(); + const oldLogsPath = path.join("..", "logs1"); + const newLogsPath = path.join("..", "logs2"); + globalMocks.mockGetConfiguration.mockReturnValue({ + get: getSettingMock, + }); + const initLoggerSpy = jest.spyOn(zowe.imperative.Logger, "initLogger").mockImplementation(); + jest.spyOn(logger.ZoweLogger as any, "initVscLogger").mockImplementation(); + + await logger.ZoweLogger.initializeZoweLogger({ + ...globalMocks.testContext, + extensionPath: oldLogsPath, + }); + await logger.ZoweLogger.initializeZoweLogger({ + ...globalMocks.testContext, + extensionPath: newLogsPath, + }); + + expect(initLoggerSpy).toHaveBeenCalledTimes(2); + const loggerConfig: Record = initLoggerSpy.mock.calls[0][0]; + expect(loggerConfig.log4jsConfig.appenders.default.filename.indexOf(oldLogsPath)).toBe(0); + const loggerConfig2: Record = initLoggerSpy.mock.calls[1][0]; + expect(loggerConfig2.log4jsConfig.appenders.default.filename.indexOf(oldLogsPath)).toBe(-1); + expect(loggerConfig2.log4jsConfig.appenders.default.filename.indexOf(newLogsPath)).toBe(0); + }); it("should throw an error if global logger was not able to initialize", async () => { const globalMocks = createGlobalMocks(); jest.spyOn(globals, "initLogger").mockImplementationOnce(() => { throw new Error("failed to initialize logger"); }); - + globalMocks.mockLogger.mockImplementationOnce(() => { + throw new Error("should not call invalid logger"); + }); const errorMessageSpy = jest.spyOn(Gui, "errorMessage").mockImplementation(); expect(await logger.ZoweLogger.initializeZoweLogger(globalMocks.testContext)).toBeUndefined(); expect(errorMessageSpy).toBeCalledTimes(1); + expect(globals.LOG).not.toHaveBeenCalled(); errorMessageSpy.mockClear(); }); it("should throw an error if output channel was not able to initialize", async () => { const globalMocks = createGlobalMocks(); - jest.spyOn(globals, "initLogger").mockImplementationOnce(() => { - return "/fake/file/path"; - }); + jest.spyOn(globals, "initLogger").mockReturnValueOnce(); jest.spyOn(Gui, "createOutputChannel").mockImplementationOnce(() => { throw new Error("failed to initialize output channel"); }); @@ -185,8 +197,7 @@ describe("It should pass the correct message severity", () => { it("ZoweLogger.trace passes TRACE as severity", async () => { const globalMocks = createGlobalMocks(); globalMocks.mockGetConfiguration.mockReturnValueOnce({ - get: (setting: string) => "TRACE", - update: jest.fn(), + get: jest.fn(() => "TRACE"), }); await logger.ZoweLogger.trace(globalMocks.mockMessage); expect(globals.LOG.trace).toHaveBeenCalled(); @@ -194,8 +205,7 @@ describe("It should pass the correct message severity", () => { it("ZoweLogger.debug passes DEBUG as severity", async () => { const globalMocks = createGlobalMocks(); globalMocks.mockGetConfiguration.mockReturnValueOnce({ - get: (setting: string) => "DEBUG", - update: jest.fn(), + get: jest.fn(() => "DEBUG"), }); await logger.ZoweLogger.debug(globalMocks.mockMessage); expect(globals.LOG.debug).toHaveBeenCalled(); @@ -203,8 +213,7 @@ describe("It should pass the correct message severity", () => { it("ZoweLogger.info passes INFO as severity", async () => { const globalMocks = createGlobalMocks(); globalMocks.mockGetConfiguration.mockReturnValueOnce({ - get: (setting: string) => "INFO", - update: jest.fn(), + get: jest.fn(() => "INFO"), }); await logger.ZoweLogger.info(globalMocks.mockMessage); expect(globals.LOG.info).toHaveBeenCalled(); @@ -212,8 +221,7 @@ describe("It should pass the correct message severity", () => { it("ZoweLogger.warn passes WARN as severity", async () => { const globalMocks = createGlobalMocks(); globalMocks.mockGetConfiguration.mockReturnValueOnce({ - get: (setting: string) => "WARN", - update: jest.fn(), + get: jest.fn(() => "WARN"), }); await logger.ZoweLogger.warn(globalMocks.mockMessage); expect(globals.LOG.warn).toHaveBeenCalled(); @@ -221,8 +229,7 @@ describe("It should pass the correct message severity", () => { it("ZoweLogger.error passes ERROR as severity", async () => { const globalMocks = createGlobalMocks(); globalMocks.mockGetConfiguration.mockReturnValueOnce({ - get: (setting: string) => "ERROR", - update: jest.fn(), + get: jest.fn(() => "ERROR"), }); await logger.ZoweLogger.error(globalMocks.mockMessage); expect(globals.LOG.error).toHaveBeenCalled(); @@ -230,8 +237,7 @@ describe("It should pass the correct message severity", () => { it("ZoweLogger.fatal passes FATAL as severity", async () => { const globalMocks = createGlobalMocks(); globalMocks.mockGetConfiguration.mockReturnValueOnce({ - get: (setting: string) => "FATAL", - update: jest.fn(), + get: jest.fn(() => "FATAL"), }); await logger.ZoweLogger.fatal(globalMocks.mockMessage); expect(globals.LOG.fatal).toHaveBeenCalled(); diff --git a/packages/zowe-explorer/__tests__/__unit__/utils/ProfilesUtils.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/utils/ProfilesUtils.unit.test.ts index e7d5442735..984c97637b 100644 --- a/packages/zowe-explorer/__tests__/__unit__/utils/ProfilesUtils.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/utils/ProfilesUtils.unit.test.ts @@ -17,6 +17,7 @@ import * as profileUtils from "../../../src/utils/ProfilesUtils"; import * as vscode from "vscode"; import * as zowe from "@zowe/cli"; import { Profiles } from "../../../src/Profiles"; +import { SettingsConfig } from "../../../src/utils/SettingsConfig"; import { ZoweLogger } from "../../../src/utils/LoggerUtils"; jest.mock("fs"); @@ -35,6 +36,7 @@ describe("ProfilesUtils unit tests", () => { mockWriteFileSync: jest.fn(), mockOpenSync: jest.fn().mockReturnValue(process.stdout.fd), mockMkdirSync: jest.fn(), + mockGetDirectValue: jest.fn(), mockFileRead: { overrides: { CredentialManager: "@zowe/cli" } }, zoweDir: path.normalize("__tests__/.zowe/settings/imperative.json"), fileHandle: process.stdout.fd, @@ -45,9 +47,10 @@ describe("ProfilesUtils unit tests", () => { Object.defineProperty(fs, "openSync", { value: newMocks.mockOpenSync, configurable: true }); Object.defineProperty(fs, "mkdirSync", { value: newMocks.mockMkdirSync, configurable: true }); Object.defineProperty(Gui, "errorMessage", { value: jest.fn(), configurable: true }); - Object.defineProperty(globals, "setGlobalSecurityValue", { value: jest.fn(), configurable: true }); + Object.defineProperty(SettingsConfig, "getDirectValue", { value: newMocks.mockGetDirectValue, configurable: true }); Object.defineProperty(globals, "LOG", { value: jest.fn(), configurable: true }); Object.defineProperty(globals.LOG, "error", { value: jest.fn(), configurable: true }); + Object.defineProperty(globals, "PROFILE_SECURITY", { value: globals.ZOWE_CLI_SCM, configurable: true }); Object.defineProperty(ZoweLogger, "error", { value: jest.fn(), configurable: true }); Object.defineProperty(ZoweLogger, "debug", { value: jest.fn(), configurable: true }); Object.defineProperty(ZoweLogger, "warn", { value: jest.fn(), configurable: true }); @@ -308,16 +311,20 @@ describe("ProfilesUtils unit tests", () => { describe("initializeZoweFolder", () => { it("should create directories and files that do not exist", async () => { const blockMocks = createBlockMocks(); + blockMocks.mockGetDirectValue.mockReturnValue(true); blockMocks.mockExistsSync.mockReturnValue(false); await profileUtils.initializeZoweFolder(); + expect(globals.PROFILE_SECURITY).toBe(globals.ZOWE_CLI_SCM); expect(blockMocks.mockMkdirSync).toHaveBeenCalledTimes(2); expect(blockMocks.mockWriteFileSync).toHaveBeenCalledTimes(1); }); it("should skip creating directories and files that already exist", async () => { const blockMocks = createBlockMocks(); + blockMocks.mockGetDirectValue.mockReturnValue(false); blockMocks.mockExistsSync.mockReturnValue(true); await profileUtils.initializeZoweFolder(); + expect(globals.PROFILE_SECURITY).toBe(false); expect(blockMocks.mockMkdirSync).toHaveBeenCalledTimes(0); expect(blockMocks.mockWriteFileSync).toHaveBeenCalledTimes(0); }); diff --git a/packages/zowe-explorer/i18n/sample/package.i18n.json b/packages/zowe-explorer/i18n/sample/package.i18n.json index dca28f5792..6715d5c9c5 100644 --- a/packages/zowe-explorer/i18n/sample/package.i18n.json +++ b/packages/zowe-explorer/i18n/sample/package.i18n.json @@ -71,6 +71,7 @@ "zowe.ds.default.pds": "Default values of Partitioned data set creation", "zowe.ds.default.ps": "Default values of Sequential data set creation", "zowe.ds.history": "Toggle if favorite files persist locally", + "zowe.files.logsFolder.path": "Path to Zowe Explorer logs folder", "zowe.files.temporaryDownloadsFolder.path": "Path to temporary folder location", "zowe.uss.history": "Toggle if USS favorite files persist locally", "zowe.jobs.history": "Toggle if Jobs favorite files persist locally", diff --git a/packages/zowe-explorer/i18n/sample/src/utils/LoggerUtils.i18n.json b/packages/zowe-explorer/i18n/sample/src/utils/LoggerUtils.i18n.json index b463f70300..1d20aba693 100644 --- a/packages/zowe-explorer/i18n/sample/src/utils/LoggerUtils.i18n.json +++ b/packages/zowe-explorer/i18n/sample/src/utils/LoggerUtils.i18n.json @@ -1,5 +1,5 @@ { - "initialize.log.error": "Error encountered while activating and initializing logger! ", + "initialize.log.error": "Error encountered while activating and initializing logger", "zoweExplorer.outputchannel.title": "Zowe Explorer", "initialize.log.info": "Initialized logger for Zowe Explorer", "initialize.log.location": "This log file can be found at {0}", diff --git a/packages/zowe-explorer/package.json b/packages/zowe-explorer/package.json index 92e439bd4d..9898fbd0dd 100644 --- a/packages/zowe-explorer/package.json +++ b/packages/zowe-explorer/package.json @@ -1696,6 +1696,12 @@ "description": "%zowe.ds.history%", "scope": "application" }, + "zowe.files.logsFolder.path": { + "type": "string", + "default": "", + "description": "%zowe.files.logsFolder.path%", + "scope": "window" + }, "zowe.files.temporaryDownloadsFolder.path": { "type": "string", "default": "", diff --git a/packages/zowe-explorer/package.nls.json b/packages/zowe-explorer/package.nls.json index dca28f5792..6715d5c9c5 100644 --- a/packages/zowe-explorer/package.nls.json +++ b/packages/zowe-explorer/package.nls.json @@ -71,6 +71,7 @@ "zowe.ds.default.pds": "Default values of Partitioned data set creation", "zowe.ds.default.ps": "Default values of Sequential data set creation", "zowe.ds.history": "Toggle if favorite files persist locally", + "zowe.files.logsFolder.path": "Path to Zowe Explorer logs folder", "zowe.files.temporaryDownloadsFolder.path": "Path to temporary folder location", "zowe.uss.history": "Toggle if USS favorite files persist locally", "zowe.jobs.history": "Toggle if Jobs favorite files persist locally", diff --git a/packages/zowe-explorer/src/dataset/actions.ts b/packages/zowe-explorer/src/dataset/actions.ts index fc6fe1f2bb..71e587ee8a 100644 --- a/packages/zowe-explorer/src/dataset/actions.ts +++ b/packages/zowe-explorer/src/dataset/actions.ts @@ -828,7 +828,7 @@ export async function submitJcl(datasetProvider: api.IZoweTree { await ZoweSaveQueue.all(); cleanTempDir(); globals.setActivated(false); - await ZoweLogger.disposeZoweLogger(); + ZoweLogger.disposeZoweLogger(); } diff --git a/packages/zowe-explorer/src/globals.ts b/packages/zowe-explorer/src/globals.ts index ba0caae045..1d4fc2e6cb 100644 --- a/packages/zowe-explorer/src/globals.ts +++ b/packages/zowe-explorer/src/globals.ts @@ -78,6 +78,7 @@ export const SETTINGS_VERSION = "zowe.settings.version"; export const SETTINGS_TEMP_FOLDER_PATH = "zowe.files.temporaryDownloadsFolder.path"; export const SETTINGS_TEMP_FOLDER_CLEANUP = "zowe.files.temporaryDownloadsFolder.cleanup"; export const SETTINGS_TEMP_FOLDER_HIDE = "zowe.files.temporaryDownloadsFolder.hide"; +export const SETTINGS_LOGS_FOLDER_PATH = "zowe.files.logsFolder.path"; export const SETTINGS_DS_DEFAULT_BINARY = "zowe.ds.default.binary"; export const SETTINGS_DS_DEFAULT_C = "zowe.ds.default.c"; export const SETTINGS_DS_DEFAULT_CLASSIC = "zowe.ds.default.classic"; @@ -309,18 +310,20 @@ export function setConfigPath(configPath: string | undefined): void { /** * Initializes Imperative Logger - * @param context The extension context + * @param logsPath File path for logs folder defined in preferences */ -export function initLogger(context: vscode.ExtensionContext): string { - for (const appenderName of Object.keys(loggerConfig.log4jsConfig.appenders)) { - loggerConfig.log4jsConfig.appenders[appenderName].filename = path.join( - context.extensionPath, - loggerConfig.log4jsConfig.appenders[appenderName].filename +export function initLogger(logsPath: string): void { + const zeLogLevel = ZoweLogger.getLogSetting(); + const loggerConfigCopy = JSON.parse(JSON.stringify(loggerConfig)); + for (const appenderName of Object.keys(loggerConfigCopy.log4jsConfig.appenders)) { + loggerConfigCopy.log4jsConfig.appenders[appenderName].filename = path.join( + logsPath, + loggerConfigCopy.log4jsConfig.appenders[appenderName].filename ); + loggerConfigCopy.log4jsConfig.categories[appenderName].level = zeLogLevel; } - imperative.Logger.initLogger(loggerConfig); + imperative.Logger.initLogger(loggerConfigCopy); this.LOG = imperative.Logger.getAppLogger(); - return loggerConfig.log4jsConfig.appenders.app.filename; } export function setActivated(value: boolean): void { @@ -337,10 +340,10 @@ export function setSavedProfileContents(value: Uint8Array): void { export async function setGlobalSecurityValue(): Promise { if (ISTHEIA) { PROFILE_SECURITY = false; - await SettingsConfig.setDirectValue(this.SETTINGS_SECURE_CREDENTIALS_ENABLED, false, vscode.ConfigurationTarget.Global); + await SettingsConfig.setDirectValue(SETTINGS_SECURE_CREDENTIALS_ENABLED, false, vscode.ConfigurationTarget.Global); return; } - const settingEnabled: boolean = SettingsConfig.getDirectValue(this.SETTINGS_SECURE_CREDENTIALS_ENABLED); + const settingEnabled: boolean = SettingsConfig.getDirectValue(SETTINGS_SECURE_CREDENTIALS_ENABLED); if (!settingEnabled) { PROFILE_SECURITY = false; ZoweLogger.info(localize("globals.setGlobalSecurityValue.unsecured", "Zowe explorer profiles are not secured.")); diff --git a/packages/zowe-explorer/src/shared/init.ts b/packages/zowe-explorer/src/shared/init.ts index d9f8829e48..fbc85f9dc5 100644 --- a/packages/zowe-explorer/src/shared/init.ts +++ b/packages/zowe-explorer/src/shared/init.ts @@ -25,6 +25,7 @@ import { saveUSSFile } from "../uss/actions"; import { promptCredentials, writeOverridesFile } from "../utils/ProfilesUtils"; import { ZoweLogger } from "../utils/LoggerUtils"; import { ZoweSaveQueue } from "../abstract/ZoweSaveQueue"; +import { SettingsConfig } from "../utils/SettingsConfig"; // Set up localization nls.config({ @@ -85,12 +86,13 @@ export function registerCommonCommands(context: vscode.ExtensionContext, provide // Register functions & event listeners context.subscriptions.push( vscode.workspace.onDidChangeConfiguration(async (e) => { + // If the log folder location has been changed, update current log folder preference + if (e.affectsConfiguration(globals.SETTINGS_LOGS_FOLDER_PATH)) { + await ZoweLogger.initializeZoweLogger(context); + } // If the temp folder location has been changed, update current temp folder preference if (e.affectsConfiguration(globals.SETTINGS_TEMP_FOLDER_PATH)) { - const updatedPreferencesTempPath: string = vscode.workspace - .getConfiguration() - /* tslint:disable:no-string-literal */ - .get(globals.SETTINGS_TEMP_FOLDER_PATH); + const updatedPreferencesTempPath: string = SettingsConfig.getDirectValue(globals.SETTINGS_TEMP_FOLDER_PATH); moveTempFolder(globals.SETTINGS_TEMP_FOLDER_LOCATION, updatedPreferencesTempPath); } if (e.affectsConfiguration(globals.SETTINGS_AUTOMATIC_PROFILE_VALIDATION)) { diff --git a/packages/zowe-explorer/src/utils/LoggerUtils.ts b/packages/zowe-explorer/src/utils/LoggerUtils.ts index 3376abf320..f31b3e6dd7 100644 --- a/packages/zowe-explorer/src/utils/LoggerUtils.ts +++ b/packages/zowe-explorer/src/utils/LoggerUtils.ts @@ -11,7 +11,7 @@ /* eslint-disable @typescript-eslint/restrict-plus-operands */ -import { Gui, MessageSeverity } from "@zowe/zowe-explorer-api"; +import { Gui, MessageSeverity, ZoweVsCodeExtension } from "@zowe/zowe-explorer-api"; import * as zowe from "@zowe/cli"; import * as vscode from "vscode"; import * as nls from "vscode-nls"; @@ -32,60 +32,66 @@ export class ZoweLogger { public static async initializeZoweLogger(context: vscode.ExtensionContext): Promise { try { - const logFileLocation = globals.initLogger(context); - await this.initVscLogger(context, logFileLocation); + const logsPath: string = ZoweVsCodeExtension.customLoggingPath ?? context.extensionPath; + globals.initLogger(logsPath); + await this.initVscLogger(context, logsPath); } catch (err) { - globals.LOG?.error(err); - const errorMessage = localize("initialize.log.error", "Error encountered while activating and initializing logger! "); + // Don't log error if logger failed to initialize if (err instanceof Error) { - await Gui.errorMessage(`${errorMessage}: ${err?.message}`); + const errorMessage = localize("initialize.log.error", "Error encountered while activating and initializing logger"); + await Gui.errorMessage(`${errorMessage}: ${err.message}`); } } } - public static async trace(message: string): Promise { - await this.writeLogMessage(message, MessageSeverity.TRACE); + public static trace(message: string): void { + this.writeLogMessage(message, MessageSeverity.TRACE); } - public static async debug(message: string): Promise { - await this.writeLogMessage(message, MessageSeverity.DEBUG); + public static debug(message: string): void { + this.writeLogMessage(message, MessageSeverity.DEBUG); } - public static async info(message: string): Promise { - await this.writeLogMessage(message, MessageSeverity.INFO); + public static info(message: string): void { + this.writeLogMessage(message, MessageSeverity.INFO); } - public static async warn(message: string): Promise { - await this.writeLogMessage(message, MessageSeverity.WARN); + public static warn(message: string): void { + this.writeLogMessage(message, MessageSeverity.WARN); } - public static async error(message: string): Promise { - await this.writeLogMessage(message, MessageSeverity.ERROR); + public static error(message: string): void { + this.writeLogMessage(message, MessageSeverity.ERROR); } - public static async fatal(message: string): Promise { - await this.writeLogMessage(message, MessageSeverity.FATAL); + public static fatal(message: string): void { + this.writeLogMessage(message, MessageSeverity.FATAL); } - public static async disposeZoweLogger(): Promise { - await this.zeOutputChannel.dispose(); + public static disposeZoweLogger(): void { + this.zeOutputChannel.dispose(); + } + + public static getLogSetting(): string { + this.zeLogLevel = vscode.workspace.getConfiguration("zowe").get("logger"); + return this.zeLogLevel ?? this.defaultLogLevel; } private static async initVscLogger(context: vscode.ExtensionContext, logFileLocation: string): Promise { this.zeOutputChannel = Gui.createOutputChannel(localize("zoweExplorer.outputchannel.title", "Zowe Explorer")); - await this.writeVscLoggerInfo(logFileLocation, context); - await this.info(localize("initialize.log.info", "Initialized logger for Zowe Explorer")); + this.writeVscLoggerInfo(logFileLocation, context); + this.info(localize("initialize.log.info", "Initialized logger for Zowe Explorer")); await this.compareCliLogSetting(); } - private static async writeVscLoggerInfo(logFileLocation: string, context: vscode.ExtensionContext): Promise { + private static writeVscLoggerInfo(logFileLocation: string, context: vscode.ExtensionContext): void { this.zeOutputChannel?.appendLine(`${context.extension.packageJSON.displayName as string} ${context.extension.packageJSON.version as string}`); this.zeOutputChannel?.appendLine(localize("initialize.log.location", "This log file can be found at {0}", logFileLocation)); - this.zeOutputChannel?.appendLine(localize("initialize.log.level", "Zowe Explorer log level: {0}", await this.getLogSetting())); + this.zeOutputChannel?.appendLine(localize("initialize.log.level", "Zowe Explorer log level: {0}", this.getLogSetting())); } - private static async writeLogMessage(message: string, severity: MessageSeverity): Promise { - if (+MessageSeverity[await this.getLogSetting()] <= +severity) { + private static writeLogMessage(message: string, severity: MessageSeverity): void { + if (+MessageSeverity[this.getLogSetting()] <= +severity) { const severityName = MessageSeverity[severity]; globals.LOG[severityName?.toLowerCase()](message); this.zeOutputChannel?.appendLine(this.createMessage(message, severityName)); @@ -98,9 +104,9 @@ export class ZoweLogger { private static async compareCliLogSetting(): Promise { const cliLogSetting = this.getZoweLogEnVar(); - const zeLogSetting = this.zeLogLevel ?? (await this.getLogSetting()); + const zeLogSetting = this.zeLogLevel ?? this.getLogSetting(); if (cliLogSetting && +MessageSeverity[zeLogSetting] !== +MessageSeverity[cliLogSetting]) { - const notified = await this.getCliLoggerSetting(); + const notified = this.getCliLoggerSetting(); if (!notified) { await this.updateVscLoggerSetting(cliLogSetting); } @@ -126,11 +132,6 @@ export class ZoweLogger { }); } - private static async getLogSetting(): Promise { - this.zeLogLevel = await vscode.workspace.getConfiguration().get("zowe.logger"); - return this.zeLogLevel ?? this.defaultLogLevel; - } - private static async setLogSetting(setting: string): Promise { await SettingsConfig.setDirectValue("zowe.logger", setting, vscode.ConfigurationTarget.Global); } @@ -139,9 +140,8 @@ export class ZoweLogger { return process.env.ZOWE_APP_LOG_LEVEL; } - private static async getCliLoggerSetting(): Promise { - const notified: boolean = await vscode.workspace.getConfiguration().get("zowe.cliLoggerSetting.presented"); - return notified ? notified : false; + private static getCliLoggerSetting(): boolean { + return SettingsConfig.getDirectValue("zowe.cliLoggerSetting.presented") ?? false; } private static async setCliLoggerSetting(setting: boolean): Promise { diff --git a/packages/zowe-explorer/src/utils/ProfilesUtils.ts b/packages/zowe-explorer/src/utils/ProfilesUtils.ts index 3332b36f74..59ef2cbf40 100644 --- a/packages/zowe-explorer/src/utils/ProfilesUtils.ts +++ b/packages/zowe-explorer/src/utils/ProfilesUtils.ts @@ -399,7 +399,7 @@ export function initializeZoweTempFolder(): void { ZoweLogger.trace("ProfilesUtils.initializeZoweTempFolder called."); try { if (!fs.existsSync(globals.ZOWETEMPFOLDER)) { - fs.mkdirSync(globals.ZOWETEMPFOLDER); + fs.mkdirSync(globals.ZOWETEMPFOLDER, { recursive: true }); fs.mkdirSync(globals.ZOWE_TMP_FOLDER); fs.mkdirSync(globals.USS_DIR); fs.mkdirSync(globals.DS_DIR); @@ -407,6 +407,6 @@ export function initializeZoweTempFolder(): void { } } catch (err) { ZoweLogger.error(err); - ZoweExplorerExtender.showZoweConfigError(err.message); + Gui.errorMessage(err.message); } }