1/* 2 * Copyright (c) 2024 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16import { GEN_ABC_PLUGIN_NAME } from './common/ark_define'; 17import { 18 ErrorCode, 19 SubsystemCode, 20 ES2ABC_ERROR_MAPPING 21} from './error_code'; 22 23interface ErrorInfo { 24 code: ErrorCode; 25 description: string; 26 cause: string; 27 position: string; 28 solutions: string[]; 29 details: string; 30} 31 32export class CommonLogger { 33 private static instance: CommonLogger; 34 private logger: Object; 35 private hvigorConsoleLoggerMap: { [key in SubsystemCode]?: Object }; 36 throwArkTsCompilerError: Object; 37 recordErrorFileId?: Object; 38 39 private constructor(rollupObject: Object) { 40 this.logger = rollupObject.share.getLogger(GEN_ABC_PLUGIN_NAME); 41 this.throwArkTsCompilerError = rollupObject.share.throwArkTsCompilerError; 42 this.recordErrorFileId = rollupObject.share?.recordErrorFileId; 43 this.hvigorConsoleLoggerMap = this.initializeHvigorConsoleLoggers(rollupObject); 44 } 45 46 private initializeHvigorConsoleLoggers(rollupObject: Object): { [key in SubsystemCode]?: Object } { 47 const loggerMap: { [key in SubsystemCode]?: Object } = {}; 48 if (typeof rollupObject.share.getHvigorConsoleLogger === 'function') { 49 loggerMap[SubsystemCode.ETS2BUNDLE] = rollupObject.share.getHvigorConsoleLogger(SubsystemCode.ETS2BUNDLE); 50 loggerMap[SubsystemCode.ABC2PROGRAM] = rollupObject.share.getHvigorConsoleLogger(SubsystemCode.ABC2PROGRAM); 51 loggerMap[SubsystemCode.ES2ABC] = rollupObject.share.getHvigorConsoleLogger(SubsystemCode.ES2ABC); 52 } 53 return loggerMap; 54 } 55 56 static getInstance(rollupObject: Object): CommonLogger { 57 if (!CommonLogger.instance) { 58 CommonLogger.instance = new CommonLogger(rollupObject); 59 } 60 return CommonLogger.instance; 61 } 62 63 static destroyInstance(): void { 64 CommonLogger.instance = undefined; 65 } 66 67 info(...args: string[]): void { 68 this.logger.info(...args); 69 } 70 71 debug(...args: string[]): void { 72 this.logger.debug(...args); 73 } 74 75 warn(...args: string[]): void { 76 this.logger.warn(...args); 77 } 78 79 error(...args: string[]): void { 80 this.logger.error(...args); 81 } 82 83 printError(error: LogData | string): void { 84 if (typeof error === 'string') { 85 this.logger.error(error); 86 return; 87 } 88 const hvigorConsoleLogger = this.getLoggerFromErrorCode(error.code); 89 if (hvigorConsoleLogger) { 90 hvigorConsoleLogger.printError(error); 91 } else { 92 this.logger.error(error.toString()); 93 } 94 } 95 96 printErrorAndExit(error: LogData | string): void { 97 if (typeof error === 'string') { 98 this.throwArkTsCompilerError(error); 99 return; 100 } 101 const hvigorConsoleLogger = this.getLoggerFromErrorCode(error.code); 102 if (hvigorConsoleLogger) { 103 hvigorConsoleLogger.printErrorAndExit(error); 104 } else { 105 this.throwArkTsCompilerError(error.toString()); 106 } 107 } 108 109 // return the file name that failed to process ohmurl during compilation. 110 returnErrorFileId(filePath?: string): void { 111 if (filePath) { 112 this?.recordErrorFileId?.(filePath); 113 } 114 } 115 116 private isValidErrorCode(errorCode: string): boolean { 117 return /^\d{8}$/.test(errorCode); 118 } 119 120 private getLoggerFromErrorCode(errorCode: string): Object | undefined { 121 if (!this.isValidErrorCode(errorCode)) { 122 return undefined; 123 } 124 const subsystemCode = errorCode.slice(0, 3); 125 return this.hvigorConsoleLoggerMap[subsystemCode]; 126 } 127} 128 129export class LogDataFactory { 130 131 static newInstance( 132 code: ErrorCode, 133 description: string, 134 cause: string = '', 135 position: string = '', 136 solutions: string[] = [] 137 ): LogData { 138 const data: LogData = new LogData(code, description, cause, position, solutions); 139 return data; 140 } 141 142 /** 143 * Parses an es2abc error string and returns a LogData instance. 144 * @param error The es2abc error string to parse. 145 * @returns A LogData instance or undefined if no matching error is found. 146 */ 147 static newInstanceFromEs2AbcError(error: string): LogData | undefined { 148 for (const prefix in ES2ABC_ERROR_MAPPING) { 149 if (error.startsWith(prefix)) { 150 const { code, description, solutions } = ES2ABC_ERROR_MAPPING[prefix]; 151 const cause = error.replace(prefix, ''); 152 return LogDataFactory.newInstance(code, description, cause, '', solutions); 153 } 154 } 155 return undefined; 156 } 157 158 static newInstanceFromBytecodeObfuscation(errorOutput: string, statusCode: number): LogData | undefined { 159 const trimmedOutput = errorOutput?.trim(); 160 if (!trimmedOutput) { 161 return LogDataFactory.newInstance( 162 ErrorCode.BYTECODE_OBFUSCATION_COMMON_ERROR, 163 'Bytecode program terminated abnormally', `Status code: ${statusCode}`); 164 } 165 166 const parseErrorLines = (output: string): Record<string, string> => 167 output.split('\n').reduce((acc: Record<string, string>, line) => { 168 const [key, ...values] = line.split(':').map(part => part.trim()); 169 return key && values.length ? { ...acc, [key]: values.join(':').trim() } : acc; 170 }, {}); 171 172 const parsedErrors = parseErrorLines(trimmedOutput); 173 174 const getErrorInfo = (): ErrorInfo => { 175 if (Object.keys(parsedErrors).length === 0) { 176 return { 177 code: ErrorCode.BYTECODE_OBFUSCATION_COMMON_ERROR, 178 description: trimmedOutput, 179 cause: '', 180 position: '', 181 solutions: [], 182 details: `Status code: ${statusCode}`, 183 }; 184 } 185 186 return { 187 code: parsedErrors['[ErrorCode]'] ?? ErrorCode.BYTECODE_OBFUSCATION_COMMON_ERROR, 188 description: parsedErrors['[Description]'] ?? parsedErrors['[Cause]'] ?? 'Unknown error', 189 cause: parsedErrors['[Cause]'] ?? '', 190 position: parsedErrors['[Position]'] ?? '', 191 solutions: parsedErrors['[Solutions]']?.split(',').filter(Boolean) ?? [], 192 details: `Status code: ${statusCode}`, 193 }; 194 }; 195 196 const { code, description, cause, position, solutions, details }: ErrorInfo = getErrorInfo(); 197 return LogDataFactory.newInstance( 198 code, 199 description, 200 details ?? cause, 201 position, 202 solutions 203 ); 204 } 205} 206 207export class LogData { 208 209 code: string; 210 description: string; 211 cause: string; 212 position: string; 213 solutions: string[]; 214 215 constructor( 216 code: ErrorCode, 217 description: string, 218 cause: string = '', 219 position: string = '', 220 solutions: string[] = [] 221 ) { 222 this.code = code; 223 this.description = description; 224 this.cause = cause; 225 this.position = position; 226 this.solutions = solutions; 227 } 228 229 toString(): string { 230 let errorString = `ERROR Code: ${this.code} ${this.description}\n`; 231 232 if (this.cause || this.position) { 233 errorString += `Error Message: ${this.cause}`; 234 if (this.position) { 235 errorString += ` ${this.position}`; 236 } 237 errorString += '\n\n'; 238 } 239 240 if (this.solutions.length > 0 && this.solutions[0] !== '') { 241 errorString += `* Try the following: \n${this.solutions.map(str => ` > ${str}`).join('\n')}\n`; 242 } 243 244 return errorString; 245 } 246}