"...composable_kernel.git" did not exist on "5173bddeba7805de22a73b09a4f101d6d36183d1"
Unverified Commit 290558c7 authored by liuzhe-lz's avatar liuzhe-lz Committed by GitHub
Browse files

Improve NNI manager logging (#3624)


Co-authored-by: default avatarliuzhe <zhe.liu@microsoft.com>
parent 60983149
...@@ -368,7 +368,7 @@ def launch_experiment(args, experiment_config, mode, experiment_id, config_versi ...@@ -368,7 +368,7 @@ def launch_experiment(args, experiment_config, mode, experiment_id, config_versi
log_dir = experiment_config['logDir'] if experiment_config.get('logDir') else NNI_HOME_DIR log_dir = experiment_config['logDir'] if experiment_config.get('logDir') else NNI_HOME_DIR
else: else:
log_dir = experiment_config['experimentWorkingDirectory'] if experiment_config.get('experimentWorkingDirectory') else NNI_HOME_DIR log_dir = experiment_config['experimentWorkingDirectory'] if experiment_config.get('experimentWorkingDirectory') else NNI_HOME_DIR
log_level = experiment_config['logLevel'] if experiment_config.get('logLevel') else None log_level = experiment_config['logLevel'] if experiment_config.get('logLevel') else 'info'
#view experiment mode do not need debug function, when view an experiment, there will be no new logs created #view experiment mode do not need debug function, when view an experiment, there will be no new logs created
foreground = False foreground = False
if mode != 'view': if mode != 'view':
......
...@@ -5,151 +5,135 @@ ...@@ -5,151 +5,135 @@
import * as fs from 'fs'; import * as fs from 'fs';
import { Writable } from 'stream'; import { Writable } from 'stream';
import { WritableStreamBuffer } from 'stream-buffers';
import { format } from 'util'; /* log level constants */
import * as component from '../common/component';
import { getExperimentStartupInfo, isReadonly } from './experimentStartupInfo'; export const DEBUG = 10;
export const INFO = 20;
const FATAL: number = 1; export const WARNING = 30;
const ERROR: number = 2; export const ERROR = 40;
const WARNING: number = 3; export const CRITICAL = 50;
const INFO: number = 4;
const DEBUG: number = 5; export const TRACE = 1;
const TRACE: number = 6; export const FATAL = 50;
const logLevelNameMap: Map<string, number> = new Map([ const levelNames = new Map<number, string>([
['fatal', FATAL], [CRITICAL, 'CRITICAL'],
['error', ERROR], [ERROR, 'ERROR'],
['warning', WARNING], [WARNING, 'WARNING'],
['info', INFO], [INFO, 'INFO'],
['debug', DEBUG], [DEBUG, 'DEBUG'],
['trace', TRACE] [TRACE, 'TRACE'],
]); ]);
class BufferSerialEmitter { /* global_ states */
private buffer: Buffer;
private emitting: boolean;
private writable: Writable;
constructor(writable: Writable) { let logFile: Writable | null = null;
this.buffer = Buffer.alloc(0); let logLevel: number = 0;
this.emitting = false; const loggers = new Map<string, Logger>();
this.writable = writable;
}
public feed(buffer: Buffer): void { /* major api */
this.buffer = Buffer.concat([this.buffer, buffer]);
if (!this.emitting) {
this.emit();
}
}
private emit(): void { export class Logger {
this.emitting = true; private name: string;
this.writable.write(this.buffer, () => {
if (this.buffer.length === 0) { constructor(name: string = 'root') {
this.emitting = false; this.name = name;
} else {
this.emit();
}
});
this.buffer = Buffer.alloc(0);
} }
}
@component.Singleton public trace(...args: any[]): void {
class Logger { this.log(TRACE, args);
private level: number = INFO; }
private bufferSerialEmitter?: BufferSerialEmitter;
private writable?: Writable;
private readonly: boolean = false;
constructor(fileName?: string) {
const logFile: string | undefined = fileName;
if (logFile) {
this.writable = fs.createWriteStream(logFile, {
flags: 'a+',
encoding: 'utf8',
autoClose: true
});
this.bufferSerialEmitter = new BufferSerialEmitter(this.writable);
}
const logLevelName: string = getExperimentStartupInfo() public debug(...args: any[]): void {
.getLogLevel(); this.log(DEBUG, args);
const logLevel: number | undefined = logLevelNameMap.get(logLevelName); }
if (logLevel !== undefined) {
this.level = logLevel;
}
this.readonly = isReadonly(); public info(...args: any[]): void {
this.log(INFO, args);
} }
public close(): void { public warning(...args: any[]): void {
if (this.writable) { this.log(WARNING, args);
this.writable.destroy();
}
} }
public trace(...param: any[]): void { public error(...args: any[]): void {
if (this.level >= TRACE) { this.log(ERROR, args);
this.log('TRACE', param);
}
} }
public debug(...param: any[]): void { public critical(...args: any[]): void {
if (this.level >= DEBUG) { this.log(CRITICAL, args);
this.log('DEBUG', param);
}
} }
public info(...param: any[]): void { public fatal(...args: any[]): void {
if (this.level >= INFO) { this.log(FATAL, args);
this.log('INFO', param);
}
} }
public warning(...param: any[]): void { private log(level: number, args: any[]): void {
if (this.level >= WARNING) { if (level < logLevel || logFile === null) {
this.log('WARNING', param); return;
} }
}
public error(...param: any[]): void { // `time.toLocaleString('sv')` trick does not work for Windows
if (this.level >= ERROR) { const isoTime = new Date(new Date().toLocaleString() + ' UTC').toISOString();
this.log('ERROR', param); const time = isoTime.slice(0, 10) + ' ' + isoTime.slice(11, 19);
const levelName = levelNames.has(level) ? levelNames.get(level) : level.toString();
const words = [];
for (const arg of args) {
if (arg === undefined) {
words.push('undefined');
} else if (arg === null) {
words.push('null');
} else if (typeof arg === 'object') {
const json = JSON.stringify(arg);
words.push(json === undefined ? arg : json);
} else {
words.push(arg);
}
} }
const message = words.join(' ');
const record = `[${time}] ${levelName} (${this.name}) ${message}\n`;
logFile.write(record);
} }
}
public fatal(...param: any[]): void { export function getLogger(name: string = 'root'): Logger {
this.log('FATAL', param); let logger = loggers.get(name);
if (logger === undefined) {
logger = new Logger(name);
loggers.set(name, logger);
} }
return logger;
/** }
* if the experiment is not in readonly mode, write log content to stream
* @param level log level /* management functions */
* @param param the params to be written
*/ export function setLogLevel(levelName: string): void {
private log(level: string, param: any[]): void { if (levelName) {
if (!this.readonly) { const level = module.exports[levelName.toUpperCase()];
const time = new Date(); if (typeof level === 'number') {
const localTime = new Date(time.getTime() - time.getTimezoneOffset() * 60000); logLevel = level;
const timeStr = localTime.toISOString().slice(0, -5).replace('T', ' '); } else {
const logContent = `[${timeStr}] ${level} ${format(param)}\n`; console.log('[ERROR] Bad log level:', levelName);
if (this.writable && this.bufferSerialEmitter) { getLogger('logging').error('Bad log level:', levelName);
const buffer: WritableStreamBuffer = new WritableStreamBuffer();
buffer.write(logContent);
buffer.end();
this.bufferSerialEmitter.feed(buffer.getContents());
} else {
console.log(logContent);
}
} }
} }
} }
function getLogger(): Logger { export function startLogging(logPath: string): void {
return component.get(Logger); logFile = fs.createWriteStream(logPath, {
flags: 'a+',
encoding: 'utf8',
autoClose: true
});
} }
export { Logger, getLogger, logLevelNameMap }; export function stopLogging(): void {
if (logFile !== null) {
logFile.end();
logFile = null;
}
}
...@@ -23,7 +23,6 @@ import { ExperimentStartupInfo, getExperimentStartupInfo, setExperimentStartupIn ...@@ -23,7 +23,6 @@ import { ExperimentStartupInfo, getExperimentStartupInfo, setExperimentStartupIn
import { ExperimentConfig, Manager } from './manager'; import { ExperimentConfig, Manager } from './manager';
import { ExperimentManager } from './experimentManager'; import { ExperimentManager } from './experimentManager';
import { HyperParameters, TrainingService, TrialJobStatus } from './trainingService'; import { HyperParameters, TrainingService, TrialJobStatus } from './trainingService';
import { logLevelNameMap } from './log';
function getExperimentRootDir(): string { function getExperimentRootDir(): string {
return getExperimentStartupInfo() return getExperimentStartupInfo()
...@@ -195,9 +194,6 @@ function prepareUnitTest(): void { ...@@ -195,9 +194,6 @@ function prepareUnitTest(): void {
Container.snapshot(ExperimentManager); Container.snapshot(ExperimentManager);
const logLevel: string = parseArg(['--log_level', '-ll']); const logLevel: string = parseArg(['--log_level', '-ll']);
if (logLevel.length > 0 && !logLevelNameMap.has(logLevel)) {
console.log(`FATAL: invalid log_level: ${logLevel}`);
}
setExperimentStartupInfo(true, 'unittest', 8080, 'unittest', undefined, logLevel); setExperimentStartupInfo(true, 'unittest', 8080, 'unittest', undefined, logLevel);
mkDirPSync(getLogDir()); mkDirPSync(getLogDir());
......
...@@ -10,7 +10,7 @@ import * as component from '../common/component'; ...@@ -10,7 +10,7 @@ import * as component from '../common/component';
import { DataStore, MetricDataRecord, MetricType, TrialJobInfo } from '../common/datastore'; import { DataStore, MetricDataRecord, MetricType, TrialJobInfo } from '../common/datastore';
import { NNIError } from '../common/errors'; import { NNIError } from '../common/errors';
import { getExperimentId, getDispatcherPipe } from '../common/experimentStartupInfo'; import { getExperimentId, getDispatcherPipe } from '../common/experimentStartupInfo';
import { getLogger, Logger } from '../common/log'; import { Logger, getLogger, stopLogging } from '../common/log';
import { import {
ExperimentProfile, Manager, ExperimentStatus, ExperimentProfile, Manager, ExperimentStatus,
NNIManagerStatus, ProfileUpdateType, TrialJobStatistics NNIManagerStatus, ProfileUpdateType, TrialJobStatistics
...@@ -362,7 +362,7 @@ class NNIManager implements Manager { ...@@ -362,7 +362,7 @@ class NNIManager implements Manager {
hasError = true; hasError = true;
this.log.error(`${err.stack}`); this.log.error(`${err.stack}`);
} finally { } finally {
this.log.close(); stopLogging();
process.exit(hasError ? 1 : 0); process.exit(hasError ? 1 : 0);
} }
} }
......
...@@ -10,7 +10,7 @@ import * as path from 'path'; ...@@ -10,7 +10,7 @@ import * as path from 'path';
import * as component from './common/component'; import * as component from './common/component';
import { Database, DataStore } from './common/datastore'; import { Database, DataStore } from './common/datastore';
import { setExperimentStartupInfo } from './common/experimentStartupInfo'; import { setExperimentStartupInfo } from './common/experimentStartupInfo';
import { getLogger, Logger, logLevelNameMap } from './common/log'; import { getLogger, setLogLevel, startLogging } from './common/log';
import { Manager, ExperimentStartUpMode } from './common/manager'; import { Manager, ExperimentStartUpMode } from './common/manager';
import { ExperimentManager } from './common/experimentManager'; import { ExperimentManager } from './common/experimentManager';
import { TensorboardManager } from './common/tensorboardManager'; import { TensorboardManager } from './common/tensorboardManager';
...@@ -47,14 +47,15 @@ async function initContainer(foreground: boolean, platformMode: string, logFileN ...@@ -47,14 +47,15 @@ async function initContainer(foreground: boolean, platformMode: string, logFileN
.to(NNITensorboardManager) .to(NNITensorboardManager)
.scope(Scope.Singleton); .scope(Scope.Singleton);
const DEFAULT_LOGFILE: string = path.join(getLogDir(), 'nnimanager.log'); const DEFAULT_LOGFILE: string = path.join(getLogDir(), 'nnimanager.log');
if (foreground) { if (!foreground) {
logFileName = undefined; if (logFileName === undefined) {
} else if (logFileName === undefined) { startLogging(DEFAULT_LOGFILE);
logFileName = DEFAULT_LOGFILE; } else {
startLogging(logFileName);
}
// eslint-disable-next-line @typescript-eslint/no-use-before-define
setLogLevel(logLevel);
} }
Container.bind(Logger).provider({
get: (): Logger => new Logger(logFileName)
});
const ds: DataStore = component.get(DataStore); const ds: DataStore = component.get(DataStore);
await ds.init(); await ds.init();
...@@ -110,9 +111,6 @@ if (logDir.length > 0) { ...@@ -110,9 +111,6 @@ if (logDir.length > 0) {
} }
const logLevel: string = parseArg(['--log_level', '-ll']); const logLevel: string = parseArg(['--log_level', '-ll']);
if (logLevel.length > 0 && !logLevelNameMap.has(logLevel)) {
console.log(`FATAL: invalid log_level: ${logLevel}`);
}
const readonlyArg: string = parseArg(['--readonly', '-r']); const readonlyArg: string = parseArg(['--readonly', '-r']);
if (!('true' || 'false').includes(readonlyArg.toLowerCase())) { if (!('true' || 'false').includes(readonlyArg.toLowerCase())) {
...@@ -132,11 +130,9 @@ mkDirP(getLogDir()) ...@@ -132,11 +130,9 @@ mkDirP(getLogDir())
await initContainer(foreground, mode); await initContainer(foreground, mode);
const restServer: NNIRestServer = component.get(NNIRestServer); const restServer: NNIRestServer = component.get(NNIRestServer);
await restServer.start(); await restServer.start();
const log: Logger = getLogger(); getLogger('main').info(`Rest server listening on: ${restServer.endPoint}`);
log.info(`Rest server listening on: ${restServer.endPoint}`);
} catch (err) { } catch (err) {
const log: Logger = getLogger(); getLogger('main').error(`${err.stack}`);
log.error(`${err.stack}`);
throw err; throw err;
} }
}) })
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment