Merge pull request #1366 from balena-io/singleton-logger

Make logger module a singleton
This commit is contained in:
bulldozer-balena[bot] 2020-06-08 16:57:23 +00:00 committed by GitHub
commit 431e022b37
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 447 additions and 627 deletions

View File

@ -28,8 +28,8 @@ import log from './lib/supervisor-console';
import DeviceState from './device-state'; import DeviceState from './device-state';
import * as globalEventBus from './event-bus'; import * as globalEventBus from './event-bus';
import Logger from './logger';
import * as TargetState from './device-state/target-state'; import * as TargetState from './device-state/target-state';
import * as logger from './logger';
// The exponential backoff starts at 15s // The exponential backoff starts at 15s
const MINIMUM_BACKOFF_DELAY = 15000; const MINIMUM_BACKOFF_DELAY = 15000;
@ -40,10 +40,6 @@ const INTERNAL_STATE_KEYS = [
'update_failed', 'update_failed',
]; ];
export interface APIBinderConstructOpts {
logger: Logger;
}
interface Device { interface Device {
id: number; id: number;
@ -67,7 +63,6 @@ export class APIBinder {
public router: express.Router; public router: express.Router;
private deviceState: DeviceState; private deviceState: DeviceState;
private logger: Logger;
public balenaApi: PinejsClientRequest | null = null; public balenaApi: PinejsClientRequest | null = null;
private lastReportedState: DeviceStatus = { private lastReportedState: DeviceStatus = {
@ -82,9 +77,7 @@ export class APIBinder {
private stateReportErrors = 0; private stateReportErrors = 0;
private readyForUpdates = false; private readyForUpdates = false;
public constructor({ logger }: APIBinderConstructOpts) { public constructor() {
this.logger = logger;
this.router = this.createAPIBinderRouter(this); this.router = this.createAPIBinderRouter(this);
} }
@ -241,12 +234,7 @@ export class APIBinder {
const lines = err.message.split(/\r?\n/); const lines = err.message.split(/\r?\n/);
lines[0] = `Could not move to new release: ${lines[0]}`; lines[0] = `Could not move to new release: ${lines[0]}`;
for (const line of lines) { for (const line of lines) {
this.logger.logSystemMessage( logger.logSystemMessage(line, {}, 'targetStateRejection', false);
line,
{},
'targetStateRejection',
false,
);
} }
} else { } else {
log.error(`Failed to get target state for device: ${err}`); log.error(`Failed to get target state for device: ${err}`);

View File

@ -4,7 +4,6 @@ import { Router } from 'express';
import Knex = require('knex'); import Knex = require('knex');
import { ServiceAction } from './device-api/common'; import { ServiceAction } from './device-api/common';
import { Logger } from './logger';
import { DeviceStatus, InstancedAppState } from './types/state'; import { DeviceStatus, InstancedAppState } from './types/state';
import ImageManager, { Image } from './compose/images'; import ImageManager, { Image } from './compose/images';
@ -46,7 +45,6 @@ class ApplicationManager extends EventEmitter {
// TODO: When the module which is/declares these fields is converted to // TODO: When the module which is/declares these fields is converted to
// typecript, type the following // typecript, type the following
public _lockingIfNecessary: any; public _lockingIfNecessary: any;
public logger: Logger;
public deviceState: DeviceState; public deviceState: DeviceState;
public apiBinder: APIBinder; public apiBinder: APIBinder;
@ -63,11 +61,7 @@ class ApplicationManager extends EventEmitter {
public router: Router; public router: Router;
public constructor({ public constructor({ deviceState: DeviceState, apiBinder: APIBinder });
logger: Logger,
deviceState: DeviceState,
apiBinder: APIBinder,
});
public init(): Promise<void>; public init(): Promise<void>;

View File

@ -9,6 +9,7 @@ import * as path from 'path';
import * as constants from './lib/constants'; import * as constants from './lib/constants';
import { log } from './lib/supervisor-console'; import { log } from './lib/supervisor-console';
import * as config from './config'; import * as config from './config';
import * as logger from './logger';
import { validateTargetContracts } from './lib/contracts'; import { validateTargetContracts } from './lib/contracts';
import { docker } from './lib/docker-utils'; import { docker } from './lib/docker-utils';
@ -79,7 +80,7 @@ const createApplicationManagerRouter = function (applications) {
}; };
export class ApplicationManager extends EventEmitter { export class ApplicationManager extends EventEmitter {
constructor({ logger, deviceState, apiBinder }) { constructor({ deviceState, apiBinder }) {
super(); super();
this.serviceAction = serviceAction; this.serviceAction = serviceAction;
@ -169,27 +170,17 @@ export class ApplicationManager extends EventEmitter {
this.removeAllVolumesForApp = this.removeAllVolumesForApp.bind(this); this.removeAllVolumesForApp = this.removeAllVolumesForApp.bind(this);
this.localModeSwitchCompletion = this.localModeSwitchCompletion.bind(this); this.localModeSwitchCompletion = this.localModeSwitchCompletion.bind(this);
this.reportOptionalContainers = this.reportOptionalContainers.bind(this); this.reportOptionalContainers = this.reportOptionalContainers.bind(this);
this.logger = logger;
this.deviceState = deviceState; this.deviceState = deviceState;
this.apiBinder = apiBinder; this.apiBinder = apiBinder;
this.images = new Images({ this.images = new Images();
logger: this.logger, this.services = new ServiceManager();
}); this.networks = new NetworkManager();
this.services = new ServiceManager({ this.volumes = new VolumeManager();
logger: this.logger,
});
this.networks = new NetworkManager({
logger: this.logger,
});
this.volumes = new VolumeManager({
logger: this.logger,
});
this.proxyvisor = new Proxyvisor({ this.proxyvisor = new Proxyvisor({
logger: this.logger,
images: this.images, images: this.images,
applications: this, applications: this,
}); });
this.localModeManager = new LocalModeManager(this.logger); this.localModeManager = new LocalModeManager();
this.timeSpentFetching = 0; this.timeSpentFetching = 0;
this.fetchesInProgress = 0; this.fetchesInProgress = 0;
this._targetVolatilePerImageId = {}; this._targetVolatilePerImageId = {};
@ -252,7 +243,7 @@ export class ApplicationManager extends EventEmitter {
.then(() => { .then(() => {
const cleanup = () => { const cleanup = () => {
return docker.listContainers({ all: true }).then((containers) => { return docker.listContainers({ all: true }).then((containers) => {
return this.logger.clearOutOfDateDBLogs(_.map(containers, 'Id')); return logger.clearOutOfDateDBLogs(_.map(containers, 'Id'));
}); });
}; };
// Rather than relying on removing out of date database entries when we're no // Rather than relying on removing out of date database entries when we're no
@ -1052,15 +1043,11 @@ export class ApplicationManager extends EventEmitter {
} }
createTargetVolume(name, appId, volume) { createTargetVolume(name, appId, volume) {
return Volume.fromComposeObject(name, appId, volume, { return Volume.fromComposeObject(name, appId, volume);
logger: this.logger,
});
} }
createTargetNetwork(name, appId, network) { createTargetNetwork(name, appId, network) {
return Network.fromComposeObject(name, appId, network, { return Network.fromComposeObject(name, appId, network);
logger: this.logger,
});
} }
normaliseAndExtendAppFromDB(app) { normaliseAndExtendAppFromDB(app) {
@ -1702,7 +1689,7 @@ export class ApplicationManager extends EventEmitter {
'. ', '. ',
)}`; )}`;
log.info(message); log.info(message);
return this.logger.logSystemMessage( return logger.logSystemMessage(
message, message,
{}, {},
'optionalContainerViolation', 'optionalContainerViolation',

View File

@ -16,7 +16,7 @@ import * as dockerUtils from '../lib/docker-utils';
import { DeltaStillProcessingError, NotFoundError } from '../lib/errors'; import { DeltaStillProcessingError, NotFoundError } from '../lib/errors';
import * as LogTypes from '../lib/log-types'; import * as LogTypes from '../lib/log-types';
import * as validation from '../lib/validation'; import * as validation from '../lib/validation';
import Logger from '../logger'; import * as logger from '../logger';
import { ImageDownloadBackoffError } from './errors'; import { ImageDownloadBackoffError } from './errors';
import log from '../lib/supervisor-console'; import log from '../lib/supervisor-console';
@ -27,10 +27,6 @@ interface ImageEvents {
type ImageEventEmitter = StrictEventEmitter<EventEmitter, ImageEvents>; type ImageEventEmitter = StrictEventEmitter<EventEmitter, ImageEvents>;
interface ImageConstructOpts {
logger: Logger;
}
interface FetchProgressEvent { interface FetchProgressEvent {
percentage: number; percentage: number;
} }
@ -57,8 +53,6 @@ type NormalisedDockerImage = Docker.ImageInfo & {
}; };
export class Images extends (EventEmitter as new () => ImageEventEmitter) { export class Images extends (EventEmitter as new () => ImageEventEmitter) {
private logger: Logger;
public appUpdatePollInterval: number; public appUpdatePollInterval: number;
private imageFetchFailures: Dictionary<number> = {}; private imageFetchFailures: Dictionary<number> = {};
@ -69,10 +63,8 @@ export class Images extends (EventEmitter as new () => ImageEventEmitter) {
// A store of volatile state for images (e.g. download progress), indexed by imageId // A store of volatile state for images (e.g. download progress), indexed by imageId
private volatileState: { [imageId: number]: Image } = {}; private volatileState: { [imageId: number]: Image } = {};
public constructor(opts: ImageConstructOpts) { public constructor() {
super(); super();
this.logger = opts.logger;
} }
public async triggerFetch( public async triggerFetch(
@ -143,7 +135,7 @@ export class Images extends (EventEmitter as new () => ImageEventEmitter) {
await db.models('image').update({ dockerImageId: id }).where(image); await db.models('image').update({ dockerImageId: id }).where(image);
this.logger.logSystemEvent(LogTypes.downloadImageSuccess, { image }); logger.logSystemEvent(LogTypes.downloadImageSuccess, { image });
success = true; success = true;
delete this.imageFetchFailures[image.name]; delete this.imageFetchFailures[image.name];
delete this.imageFetchLastFailureTime[image.name]; delete this.imageFetchLastFailureTime[image.name];
@ -152,10 +144,10 @@ export class Images extends (EventEmitter as new () => ImageEventEmitter) {
// If this is a delta image pull, and the delta still hasn't finished generating, // If this is a delta image pull, and the delta still hasn't finished generating,
// don't show a failure message, and instead just inform the user that it's remotely // don't show a failure message, and instead just inform the user that it's remotely
// processing // processing
this.logger.logSystemEvent(LogTypes.deltaStillProcessingError, {}); logger.logSystemEvent(LogTypes.deltaStillProcessingError, {});
} else { } else {
this.addImageFailure(image.name); this.addImageFailure(image.name);
this.logger.logSystemEvent(LogTypes.downloadImageError, { logger.logSystemEvent(LogTypes.downloadImageError, {
image, image,
error: err, error: err,
}); });
@ -173,7 +165,7 @@ export class Images extends (EventEmitter as new () => ImageEventEmitter) {
try { try {
await this.removeImageIfNotNeeded(image); await this.removeImageIfNotNeeded(image);
} catch (e) { } catch (e) {
this.logger.logSystemEvent(LogTypes.deleteImageError, { logger.logSystemEvent(LogTypes.deleteImageError, {
image, image,
error: e, error: e,
}); });
@ -439,7 +431,7 @@ export class Images extends (EventEmitter as new () => ImageEventEmitter) {
await docker.getImage(image).remove({ force: true }); await docker.getImage(image).remove({ force: true });
delete this.imageCleanupFailures[image]; delete this.imageCleanupFailures[image];
} catch (e) { } catch (e) {
this.logger.logSystemMessage( logger.logSystemMessage(
`Error cleaning up ${image}: ${e.message} - will ignore for 1 hour`, `Error cleaning up ${image}: ${e.message} - will ignore for 1 hour`,
{ error: e }, { error: e },
'Image cleanup error', 'Image cleanup error',
@ -512,7 +504,7 @@ export class Images extends (EventEmitter as new () => ImageEventEmitter) {
image.imageId, image.imageId,
_.merge(_.clone(image), { status: 'Deleting' }), _.merge(_.clone(image), { status: 'Deleting' }),
); );
this.logger.logSystemEvent(LogTypes.deleteImage, { image }); logger.logSystemEvent(LogTypes.deleteImage, { image });
docker.getImage(img.dockerImageId).remove({ force: true }); docker.getImage(img.dockerImageId).remove({ force: true });
removed = true; removed = true;
} else if (!Images.hasDigest(img.name)) { } else if (!Images.hasDigest(img.name)) {
@ -549,7 +541,7 @@ export class Images extends (EventEmitter as new () => ImageEventEmitter) {
await db.models('image').del().where({ id: img.id }); await db.models('image').del().where({ id: img.id });
if (removed) { if (removed) {
this.logger.logSystemEvent(LogTypes.deleteImageSuccess, { image }); logger.logSystemEvent(LogTypes.deleteImageSuccess, { image });
} }
} }
@ -584,7 +576,7 @@ export class Images extends (EventEmitter as new () => ImageEventEmitter) {
onProgress: (evt: FetchProgressEvent) => void, onProgress: (evt: FetchProgressEvent) => void,
serviceName: string, serviceName: string,
): Promise<string> { ): Promise<string> {
this.logger.logSystemEvent(LogTypes.downloadImageDelta, { image }); logger.logSystemEvent(LogTypes.downloadImageDelta, { image });
const deltaOpts = (opts as unknown) as DeltaFetchOptions; const deltaOpts = (opts as unknown) as DeltaFetchOptions;
const srcImage = await this.inspectByName(deltaOpts.deltaSource); const srcImage = await this.inspectByName(deltaOpts.deltaSource);
@ -610,7 +602,7 @@ export class Images extends (EventEmitter as new () => ImageEventEmitter) {
opts: FetchOptions, opts: FetchOptions,
onProgress: (evt: FetchProgressEvent) => void, onProgress: (evt: FetchProgressEvent) => void,
): Promise<string> { ): Promise<string> {
this.logger.logSystemEvent(LogTypes.downloadImage, { image }); logger.logSystemEvent(LogTypes.downloadImage, { image });
return dockerUtils.fetchImageWithProgress(image.name, opts, onProgress); return dockerUtils.fetchImageWithProgress(image.name, opts, onProgress);
} }

View File

@ -6,31 +6,20 @@ import * as constants from '../lib/constants';
import { docker } from '../lib/docker-utils'; import { docker } from '../lib/docker-utils';
import { ENOENT, NotFoundError } from '../lib/errors'; import { ENOENT, NotFoundError } from '../lib/errors';
import logTypes = require('../lib/log-types'); import logTypes = require('../lib/log-types');
import { Logger } from '../logger'; import * as logger from '../logger';
import { Network, NetworkOptions } from './network'; import { Network } from './network';
import log from '../lib/supervisor-console'; import log from '../lib/supervisor-console';
import { ResourceRecreationAttemptError } from './errors'; import { ResourceRecreationAttemptError } from './errors';
export class NetworkManager { export class NetworkManager {
private logger: Logger;
constructor(opts: NetworkOptions) {
this.logger = opts.logger;
}
public getAll(): Bluebird<Network[]> { public getAll(): Bluebird<Network[]> {
return this.getWithBothLabels().map((network: { Name: string }) => { return this.getWithBothLabels().map((network: { Name: string }) => {
return docker return docker
.getNetwork(network.Name) .getNetwork(network.Name)
.inspect() .inspect()
.then((net) => { .then((net) => {
return Network.fromDockerNetwork( return Network.fromDockerNetwork(net);
{
logger: this.logger,
},
net,
);
}); });
}); });
} }
@ -43,7 +32,7 @@ export class NetworkManager {
const dockerNet = await docker const dockerNet = await docker
.getNetwork(Network.generateDockerName(network.appId, network.name)) .getNetwork(Network.generateDockerName(network.appId, network.name))
.inspect(); .inspect();
return Network.fromDockerNetwork({ logger: this.logger }, dockerNet); return Network.fromDockerNetwork(dockerNet);
} }
public async create(network: Network) { public async create(network: Network) {
@ -60,7 +49,7 @@ export class NetworkManager {
// already created, we can skip this // already created, we can skip this
} catch (e) { } catch (e) {
if (!NotFoundError(e)) { if (!NotFoundError(e)) {
this.logger.logSystemEvent(logTypes.createNetworkError, { logger.logSystemEvent(logTypes.createNetworkError, {
network: { name: network.name, appId: network.appId }, network: { name: network.name, appId: network.appId },
error: e, error: e,
}); });

View File

@ -5,7 +5,7 @@ import { docker } from '../lib/docker-utils';
import { InvalidAppIdError } from '../lib/errors'; import { InvalidAppIdError } from '../lib/errors';
import logTypes = require('../lib/log-types'); import logTypes = require('../lib/log-types');
import { checkInt } from '../lib/validation'; import { checkInt } from '../lib/validation';
import { Logger } from '../logger'; import * as logger from '../logger';
import * as ComposeUtils from './utils'; import * as ComposeUtils from './utils';
import { import {
@ -21,26 +21,15 @@ import {
InvalidNetworkNameError, InvalidNetworkNameError,
} from './errors'; } from './errors';
export interface NetworkOptions {
logger: Logger;
}
export class Network { export class Network {
public appId: number; public appId: number;
public name: string; public name: string;
public config: NetworkConfig; public config: NetworkConfig;
private logger: Logger; private constructor() {}
private constructor(opts: NetworkOptions) { public static fromDockerNetwork(network: NetworkInspect): Network {
this.logger = opts.logger; const ret = new Network();
}
public static fromDockerNetwork(
opts: NetworkOptions,
network: NetworkInspect,
): Network {
const ret = new Network(opts);
const match = network.Name.match(/^([0-9]+)_(.+)$/); const match = network.Name.match(/^([0-9]+)_(.+)$/);
if (match == null) { if (match == null) {
@ -94,9 +83,8 @@ export class Network {
network: Partial<Omit<ComposeNetworkConfig, 'ipam'>> & { network: Partial<Omit<ComposeNetworkConfig, 'ipam'>> & {
ipam?: Partial<ComposeNetworkConfig['ipam']>; ipam?: Partial<ComposeNetworkConfig['ipam']>;
}, },
opts: NetworkOptions,
): Network { ): Network {
const net = new Network(opts); const net = new Network();
net.name = name; net.name = name;
net.appId = appId; net.appId = appId;
@ -138,7 +126,7 @@ export class Network {
} }
public async create(): Promise<void> { public async create(): Promise<void> {
this.logger.logSystemEvent(logTypes.createNetwork, { logger.logSystemEvent(logTypes.createNetwork, {
network: { name: this.name }, network: { name: this.name },
}); });
@ -183,7 +171,7 @@ export class Network {
} }
public remove(): Bluebird<void> { public remove(): Bluebird<void> {
this.logger.logSystemEvent(logTypes.removeNetwork, { logger.logSystemEvent(logTypes.removeNetwork, {
network: { name: this.name, appId: this.appId }, network: { name: this.name, appId: this.appId },
}); });
@ -192,7 +180,7 @@ export class Network {
.getNetwork(Network.generateDockerName(this.appId, this.name)) .getNetwork(Network.generateDockerName(this.appId, this.name))
.remove(), .remove(),
).tapCatch((error) => { ).tapCatch((error) => {
this.logger.logSystemEvent(logTypes.removeNetworkError, { logger.logSystemEvent(logTypes.removeNetworkError, {
network: { name: this.name, appId: this.appId }, network: { name: this.name, appId: this.appId },
error, error,
}); });

View File

@ -9,7 +9,7 @@ import StrictEventEmitter from 'strict-event-emitter-types';
import * as config from '../config'; import * as config from '../config';
import { docker } from '../lib/docker-utils'; import { docker } from '../lib/docker-utils';
import Logger from '../logger'; import * as logger from '../logger';
import { PermissiveNumber } from '../config/types'; import { PermissiveNumber } from '../config/types';
import constants = require('../lib/constants'); import constants = require('../lib/constants');
@ -25,10 +25,6 @@ import { serviceNetworksToDockerNetworks } from './utils';
import log from '../lib/supervisor-console'; import log from '../lib/supervisor-console';
interface ServiceConstructOpts {
logger: Logger;
}
interface ServiceManagerEvents { interface ServiceManagerEvents {
change: void; change: void;
} }
@ -43,8 +39,6 @@ interface KillOpts {
} }
export class ServiceManager extends (EventEmitter as new () => ServiceManagerEventEmitter) { export class ServiceManager extends (EventEmitter as new () => ServiceManagerEventEmitter) {
private logger: Logger;
// Whether a container has died, indexed by ID // Whether a container has died, indexed by ID
private containerHasDied: Dictionary<boolean> = {}; private containerHasDied: Dictionary<boolean> = {};
private listening = false; private listening = false;
@ -52,9 +46,8 @@ export class ServiceManager extends (EventEmitter as new () => ServiceManagerEve
// we don't yet have an id) // we don't yet have an id)
private volatileState: Dictionary<Partial<Service>> = {}; private volatileState: Dictionary<Partial<Service>> = {};
public constructor(opts: ServiceConstructOpts) { public constructor() {
super(); super();
this.logger = opts.logger;
} }
public async getAll( public async getAll(
@ -199,7 +192,7 @@ export class ServiceManager extends (EventEmitter as new () => ServiceManagerEve
} }
public async remove(service: Service) { public async remove(service: Service) {
this.logger.logSystemEvent(LogTypes.removeDeadService, { service }); logger.logSystemEvent(LogTypes.removeDeadService, { service });
const existingService = await this.get(service); const existingService = await this.get(service);
if (existingService.containerId == null) { if (existingService.containerId == null) {
@ -214,7 +207,7 @@ export class ServiceManager extends (EventEmitter as new () => ServiceManagerEve
.remove({ v: true }); .remove({ v: true });
} catch (e) { } catch (e) {
if (!NotFoundError(e)) { if (!NotFoundError(e)) {
this.logger.logSystemEvent(LogTypes.removeDeadServiceError, { logger.logSystemEvent(LogTypes.removeDeadServiceError, {
service, service,
error: e, error: e,
}); });
@ -244,7 +237,7 @@ export class ServiceManager extends (EventEmitter as new () => ServiceManagerEve
return docker.getContainer(existing.containerId); return docker.getContainer(existing.containerId);
} catch (e) { } catch (e) {
if (!NotFoundError(e)) { if (!NotFoundError(e)) {
this.logger.logSystemEvent(LogTypes.installServiceError, { logger.logSystemEvent(LogTypes.installServiceError, {
service, service,
error: e, error: e,
}); });
@ -274,7 +267,7 @@ export class ServiceManager extends (EventEmitter as new () => ServiceManagerEve
service.extraNetworksToJoin(), service.extraNetworksToJoin(),
); );
this.logger.logSystemEvent(LogTypes.installService, { service }); logger.logSystemEvent(LogTypes.installService, { service });
this.reportNewStatus(mockContainerId, service, 'Installing'); this.reportNewStatus(mockContainerId, service, 'Installing');
const container = await docker.createContainer(conf); const container = await docker.createContainer(conf);
@ -289,7 +282,7 @@ export class ServiceManager extends (EventEmitter as new () => ServiceManagerEve
), ),
); );
this.logger.logSystemEvent(LogTypes.installServiceSuccess, { service }); logger.logSystemEvent(LogTypes.installServiceSuccess, { service });
return container; return container;
} finally { } finally {
this.reportChange(mockContainerId); this.reportChange(mockContainerId);
@ -303,7 +296,7 @@ export class ServiceManager extends (EventEmitter as new () => ServiceManagerEve
try { try {
const container = await this.create(service); const container = await this.create(service);
containerId = container.id; containerId = container.id;
this.logger.logSystemEvent(LogTypes.startService, { service }); logger.logSystemEvent(LogTypes.startService, { service });
this.reportNewStatus(containerId, service, 'Starting'); this.reportNewStatus(containerId, service, 'Starting');
@ -348,7 +341,7 @@ export class ServiceManager extends (EventEmitter as new () => ServiceManagerEve
if (remove) { if (remove) {
// If starting the container fialed, we remove it so that it doesn't litter // If starting the container fialed, we remove it so that it doesn't litter
await container.remove({ v: true }).catch(_.noop); await container.remove({ v: true }).catch(_.noop);
this.logger.logSystemEvent(LogTypes.startServiceError, { logger.logSystemEvent(LogTypes.startServiceError, {
service, service,
error: err, error: err,
}); });
@ -363,10 +356,10 @@ export class ServiceManager extends (EventEmitter as new () => ServiceManagerEve
); );
} }
this.logger.attach(container.id, { serviceId, imageId }); logger.attach(container.id, { serviceId, imageId });
if (!alreadyStarted) { if (!alreadyStarted) {
this.logger.logSystemEvent(LogTypes.startServiceSuccess, { service }); logger.logSystemEvent(LogTypes.startServiceSuccess, { service });
} }
service.config.running = true; service.config.running = true;
@ -410,14 +403,14 @@ export class ServiceManager extends (EventEmitter as new () => ServiceManagerEve
if (service != null) { if (service != null) {
this.emit('change'); this.emit('change');
if (status === 'die') { if (status === 'die') {
this.logger.logSystemEvent(LogTypes.serviceExit, { service }); logger.logSystemEvent(LogTypes.serviceExit, { service });
this.containerHasDied[data.id] = true; this.containerHasDied[data.id] = true;
} else if ( } else if (
status === 'start' && status === 'start' &&
this.containerHasDied[data.id] this.containerHasDied[data.id]
) { ) {
delete this.containerHasDied[data.id]; delete this.containerHasDied[data.id];
this.logger.logSystemEvent(LogTypes.serviceRestart, { logger.logSystemEvent(LogTypes.serviceRestart, {
service, service,
}); });
@ -428,7 +421,7 @@ export class ServiceManager extends (EventEmitter as new () => ServiceManagerEve
`serviceId and imageId not defined for service: ${service.serviceName} in ServiceManager.listenToEvents`, `serviceId and imageId not defined for service: ${service.serviceName} in ServiceManager.listenToEvents`,
); );
} }
this.logger.attach(data.id, { logger.attach(data.id, {
serviceId, serviceId,
imageId, imageId,
}); });
@ -481,7 +474,7 @@ export class ServiceManager extends (EventEmitter as new () => ServiceManagerEve
`containerId not defined for service: ${service.serviceName} in ServiceManager.attachToRunning`, `containerId not defined for service: ${service.serviceName} in ServiceManager.attachToRunning`,
); );
} }
this.logger.attach(service.containerId, { logger.attach(service.containerId, {
serviceId, serviceId,
imageId, imageId,
}); });
@ -533,7 +526,7 @@ export class ServiceManager extends (EventEmitter as new () => ServiceManagerEve
// TODO: Remove the need for the wait flag // TODO: Remove the need for the wait flag
return Bluebird.try(() => { return Bluebird.try(() => {
this.logger.logSystemEvent(LogTypes.stopService, { service }); logger.logSystemEvent(LogTypes.stopService, { service });
if (service.imageId != null) { if (service.imageId != null) {
this.reportNewStatus(containerId, service, 'Stopping'); this.reportNewStatus(containerId, service, 'Stopping');
} }
@ -558,14 +551,14 @@ export class ServiceManager extends (EventEmitter as new () => ServiceManagerEve
// 304 means the container was already stopped, so we can just remove it // 304 means the container was already stopped, so we can just remove it
if (statusCode === 304) { if (statusCode === 304) {
this.logger.logSystemEvent(LogTypes.stopServiceNoop, { service }); logger.logSystemEvent(LogTypes.stopServiceNoop, { service });
// Why do we attempt to remove the container again? // Why do we attempt to remove the container again?
if (removeContainer) { if (removeContainer) {
return containerObj.remove({ v: true }); return containerObj.remove({ v: true });
} }
} else if (statusCode === 404) { } else if (statusCode === 404) {
// 404 means the container doesn't exist, precisely what we want! // 404 means the container doesn't exist, precisely what we want!
this.logger.logSystemEvent(LogTypes.stopRemoveServiceNoop, { logger.logSystemEvent(LogTypes.stopRemoveServiceNoop, {
service, service,
}); });
} else { } else {
@ -574,10 +567,10 @@ export class ServiceManager extends (EventEmitter as new () => ServiceManagerEve
}) })
.tap(() => { .tap(() => {
delete this.containerHasDied[containerId]; delete this.containerHasDied[containerId];
this.logger.logSystemEvent(LogTypes.stopServiceSuccess, { service }); logger.logSystemEvent(LogTypes.stopServiceSuccess, { service });
}) })
.catch((e) => { .catch((e) => {
this.logger.logSystemEvent(LogTypes.stopServiceError, { logger.logSystemEvent(LogTypes.stopServiceError, {
service, service,
error: e, error: e,
}); });

View File

@ -8,38 +8,25 @@ import { safeRename } from '../lib/fs-utils';
import { docker } from '../lib/docker-utils'; import { docker } from '../lib/docker-utils';
import * as LogTypes from '../lib/log-types'; import * as LogTypes from '../lib/log-types';
import { defaultLegacyVolume } from '../lib/migration'; import { defaultLegacyVolume } from '../lib/migration';
import Logger from '../logger'; import * as logger from '../logger';
import { ResourceRecreationAttemptError } from './errors'; import { ResourceRecreationAttemptError } from './errors';
import Volume, { VolumeConfig } from './volume'; import Volume, { VolumeConfig } from './volume';
export interface VolumeMangerConstructOpts {
logger: Logger;
}
export interface VolumeNameOpts { export interface VolumeNameOpts {
name: string; name: string;
appId: number; appId: number;
} }
export class VolumeManager { export class VolumeManager {
private logger: Logger;
public constructor(opts: VolumeMangerConstructOpts) {
this.logger = opts.logger;
}
public async get({ name, appId }: VolumeNameOpts): Promise<Volume> { public async get({ name, appId }: VolumeNameOpts): Promise<Volume> {
return Volume.fromDockerVolume( return Volume.fromDockerVolume(
{ logger: this.logger },
await docker.getVolume(Volume.generateDockerName(appId, name)).inspect(), await docker.getVolume(Volume.generateDockerName(appId, name)).inspect(),
); );
} }
public async getAll(): Promise<Volume[]> { public async getAll(): Promise<Volume[]> {
const volumeInspect = await this.listWithBothLabels(); const volumeInspect = await this.listWithBothLabels();
return volumeInspect.map((inspect) => return volumeInspect.map((inspect) => Volume.fromDockerVolume(inspect));
Volume.fromDockerVolume({ logger: this.logger }, inspect),
);
} }
public async getAllByAppId(appId: number): Promise<Volume[]> { public async getAllByAppId(appId: number): Promise<Volume[]> {
@ -61,7 +48,7 @@ export class VolumeManager {
} }
} catch (e) { } catch (e) {
if (!NotFoundError(e)) { if (!NotFoundError(e)) {
this.logger.logSystemEvent(LogTypes.createVolumeError, { logger.logSystemEvent(LogTypes.createVolumeError, {
volume: { name: volume.name }, volume: { name: volume.name },
error: e, error: e,
}); });
@ -89,7 +76,7 @@ export class VolumeManager {
try { try {
return await this.createFromPath({ name, appId }, {}, legacyPath); return await this.createFromPath({ name, appId }, {}, legacyPath);
} catch (e) { } catch (e) {
this.logger.logSystemMessage( logger.logSystemMessage(
`Warning: could not migrate legacy /data volume: ${e.message}`, `Warning: could not migrate legacy /data volume: ${e.message}`,
{ error: e }, { error: e },
'Volume migration error', 'Volume migration error',
@ -102,9 +89,7 @@ export class VolumeManager {
config: Partial<VolumeConfig>, config: Partial<VolumeConfig>,
oldPath: string, oldPath: string,
): Promise<Volume> { ): Promise<Volume> {
const volume = Volume.fromComposeObject(name, appId, config, { const volume = Volume.fromComposeObject(name, appId, config);
logger: this.logger,
});
await this.create(volume); await this.create(volume);
const inspect = await docker const inspect = await docker

View File

@ -8,13 +8,9 @@ import { docker } from '../lib/docker-utils';
import { InternalInconsistencyError } from '../lib/errors'; import { InternalInconsistencyError } from '../lib/errors';
import * as LogTypes from '../lib/log-types'; import * as LogTypes from '../lib/log-types';
import { LabelObject } from '../lib/types'; import { LabelObject } from '../lib/types';
import Logger from '../logger'; import * as logger from '../logger';
import * as ComposeUtils from './utils'; import * as ComposeUtils from './utils';
export interface VolumeConstructOpts {
logger: Logger;
}
export interface VolumeConfig { export interface VolumeConfig {
labels: LabelObject; labels: LabelObject;
driver: string; driver: string;
@ -28,21 +24,13 @@ export interface ComposeVolumeConfig {
} }
export class Volume { export class Volume {
private logger: Logger;
private constructor( private constructor(
public name: string, public name: string,
public appId: number, public appId: number,
public config: VolumeConfig, public config: VolumeConfig,
opts: VolumeConstructOpts, ) {}
) {
this.logger = opts.logger;
}
public static fromDockerVolume( public static fromDockerVolume(inspect: Docker.VolumeInspectInfo): Volume {
opts: VolumeConstructOpts,
inspect: Docker.VolumeInspectInfo,
): Volume {
// Convert the docker inspect to the config // Convert the docker inspect to the config
const config: VolumeConfig = { const config: VolumeConfig = {
labels: inspect.Labels || {}, labels: inspect.Labels || {},
@ -53,14 +41,13 @@ export class Volume {
// Detect the name and appId from the inspect data // Detect the name and appId from the inspect data
const { name, appId } = this.deconstructDockerName(inspect.Name); const { name, appId } = this.deconstructDockerName(inspect.Name);
return new Volume(name, appId, config, opts); return new Volume(name, appId, config);
} }
public static fromComposeObject( public static fromComposeObject(
name: string, name: string,
appId: number, appId: number,
config: Partial<ComposeVolumeConfig>, config: Partial<ComposeVolumeConfig>,
opts: VolumeConstructOpts,
) { ) {
const filledConfig: VolumeConfig = { const filledConfig: VolumeConfig = {
driverOpts: config.driver_opts || {}, driverOpts: config.driver_opts || {},
@ -72,7 +59,7 @@ export class Volume {
// get it from the daemon, they should already be there // get it from the daemon, they should already be there
assign(filledConfig.labels, constants.defaultVolumeLabels); assign(filledConfig.labels, constants.defaultVolumeLabels);
return new Volume(name, appId, filledConfig, opts); return new Volume(name, appId, filledConfig);
} }
public toComposeObject(): ComposeVolumeConfig { public toComposeObject(): ComposeVolumeConfig {
@ -95,7 +82,7 @@ export class Volume {
} }
public async create(): Promise<void> { public async create(): Promise<void> {
this.logger.logSystemEvent(LogTypes.createVolume, { logger.logSystemEvent(LogTypes.createVolume, {
volume: { name: this.name }, volume: { name: this.name },
}); });
await docker.createVolume({ await docker.createVolume({
@ -107,7 +94,7 @@ export class Volume {
} }
public async remove(): Promise<void> { public async remove(): Promise<void> {
this.logger.logSystemEvent(LogTypes.removeVolume, { logger.logSystemEvent(LogTypes.removeVolume, {
volume: { name: this.name }, volume: { name: this.name },
}); });
@ -116,7 +103,7 @@ export class Volume {
.getVolume(Volume.generateDockerName(this.appId, this.name)) .getVolume(Volume.generateDockerName(this.appId, this.name))
.remove(); .remove();
} catch (e) { } catch (e) {
this.logger.logSystemEvent(LogTypes.removeVolumeError, { logger.logSystemEvent(LogTypes.removeVolumeError, {
volume: { name: this.name, appId: this.appId }, volume: { name: this.name, appId: this.appId },
error: e, error: e,
}); });

View File

@ -6,7 +6,7 @@ import * as constants from '../lib/constants';
import { writeFileAtomic } from '../lib/fs-utils'; import { writeFileAtomic } from '../lib/fs-utils';
import log from '../lib/supervisor-console'; import log from '../lib/supervisor-console';
import Logger from '../logger'; import * as logger from '../logger';
export interface ConfigOptions { export interface ConfigOptions {
[key: string]: string | string[]; [key: string]: string | string[];
@ -34,13 +34,7 @@ async function remountAndWriteAtomic(
await writeFileAtomic(file, data); await writeFileAtomic(file, data);
} }
export interface BackendOptions {
logger?: Logger;
}
export abstract class DeviceConfigBackend { export abstract class DeviceConfigBackend {
protected options: BackendOptions = {};
// Does this config backend support the given device type? // Does this config backend support the given device type?
public abstract matches(deviceType: string): boolean; public abstract matches(deviceType: string): boolean;
@ -74,8 +68,7 @@ export abstract class DeviceConfigBackend {
public abstract createConfigVarName(configName: string): string; public abstract createConfigVarName(configName: string): string;
// Allow a chosen config backend to be initialised // Allow a chosen config backend to be initialised
public async initialise(opts: BackendOptions): Promise<DeviceConfigBackend> { public async initialise(): Promise<DeviceConfigBackend> {
this.options = { ...this.options, ...opts };
return this; return this;
} }
} }
@ -484,8 +477,8 @@ export class ConfigfsConfigBackend extends DeviceConfigBackend {
// log to system log if the AML doesn't exist... // log to system log if the AML doesn't exist...
if (!(await fs.exists(amlSrcPath))) { if (!(await fs.exists(amlSrcPath))) {
log.error(`Missing AML for \'${aml}\'. Unable to load.`); log.error(`Missing AML for \'${aml}\'. Unable to load.`);
if (this.options.logger) { if (logger) {
this.options.logger.logSystemMessage( logger.logSystemMessage(
`Missing AML for \'${aml}\'. Unable to load.`, `Missing AML for \'${aml}\'. Unable to load.`,
{ aml, path: amlSrcPath }, { aml, path: amlSrcPath },
'Load AML error', 'Load AML error',
@ -555,11 +548,9 @@ export class ConfigfsConfigBackend extends DeviceConfigBackend {
} }
} }
public async initialise( public async initialise(): Promise<ConfigfsConfigBackend> {
opts: BackendOptions,
): Promise<ConfigfsConfigBackend> {
try { try {
await super.initialise(opts); await super.initialise();
// load the acpi_configfs module... // load the acpi_configfs module...
await child_process.exec('modprobe acpi_configfs'); await child_process.exec('modprobe acpi_configfs');
@ -575,14 +566,13 @@ export class ConfigfsConfigBackend extends DeviceConfigBackend {
log.success('Initialised ConfigFS'); log.success('Initialised ConfigFS');
} catch (error) { } catch (error) {
log.error(error); log.error(error);
if (this.options.logger) { await logger.initialized;
this.options.logger.logSystemMessage( logger.logSystemMessage(
'Unable to initialise ConfigFS', 'Unable to initialise ConfigFS',
{ error }, { error },
'ConfigFS initialisation error', 'ConfigFS initialisation error',
); );
} }
}
return this; return this;
} }

View File

@ -2,7 +2,6 @@ import * as _ from 'lodash';
import { EnvVarObject } from '../lib/types'; import { EnvVarObject } from '../lib/types';
import { import {
BackendOptions,
ConfigfsConfigBackend, ConfigfsConfigBackend,
ConfigOptions, ConfigOptions,
DeviceConfigBackend, DeviceConfigBackend,
@ -16,13 +15,10 @@ const configBackends = [
new ConfigfsConfigBackend(), new ConfigfsConfigBackend(),
]; ];
export const initialiseConfigBackend = async ( export const initialiseConfigBackend = async (deviceType: string) => {
deviceType: string,
opts: BackendOptions,
) => {
const backend = getConfigBackend(deviceType); const backend = getConfigBackend(deviceType);
if (backend) { if (backend) {
await backend.initialise(opts); await backend.initialise();
return backend; return backend;
} }
}; };

View File

@ -7,6 +7,7 @@ import { Service } from '../compose/service';
import Volume from '../compose/volume'; import Volume from '../compose/volume';
import * as config from '../config'; import * as config from '../config';
import * as db from '../db'; import * as db from '../db';
import * as logger from '../logger';
import { spawnJournalctl } from '../lib/journald'; import { spawnJournalctl } from '../lib/journald';
import { import {
appNotFoundMessage, appNotFoundMessage,
@ -323,7 +324,7 @@ export function createV2Api(router: Router, applications: ApplicationManager) {
router.get('/v2/local/logs', async (_req, res) => { router.get('/v2/local/logs', async (_req, res) => {
const serviceNameCache: { [sId: number]: string } = {}; const serviceNameCache: { [sId: number]: string } = {};
const backend = applications.logger.getLocalBackend(); const backend = logger.getLocalBackend();
// Cache the service names to IDs per call to the endpoint // Cache the service names to IDs per call to the endpoint
backend.assignServiceNameResolver(async (id: number) => { backend.assignServiceNameResolver(async (id: number) => {
if (id in serviceNameCache) { if (id in serviceNameCache) {

View File

@ -4,7 +4,7 @@ import { inspect } from 'util';
import * as config from './config'; import * as config from './config';
import { SchemaTypeKey } from './config/schema-type'; import { SchemaTypeKey } from './config/schema-type';
import * as db from './db'; import * as db from './db';
import Logger from './logger'; import * as logger from './logger';
import { ConfigOptions, DeviceConfigBackend } from './config/backend'; import { ConfigOptions, DeviceConfigBackend } from './config/backend';
import * as configUtils from './config/utils'; import * as configUtils from './config/utils';
@ -16,10 +16,6 @@ import { DeviceStatus } from './types/state';
const vpnServiceName = 'openvpn'; const vpnServiceName = 'openvpn';
interface DeviceConfigConstructOpts {
logger: Logger;
}
interface ConfigOption { interface ConfigOption {
envVarName: string; envVarName: string;
varType: string; varType: string;
@ -55,7 +51,6 @@ interface DeviceActionExecutors {
} }
export class DeviceConfig { export class DeviceConfig {
private logger: Logger;
private rebootRequired = false; private rebootRequired = false;
private actionExecutors: DeviceActionExecutors; private actionExecutors: DeviceActionExecutors;
private configBackend: DeviceConfigBackend | null = null; private configBackend: DeviceConfigBackend | null = null;
@ -146,14 +141,12 @@ export class DeviceConfig {
}, },
}; };
public constructor({ logger }: DeviceConfigConstructOpts) { public constructor() {
this.logger = logger;
this.actionExecutors = { this.actionExecutors = {
changeConfig: async (step) => { changeConfig: async (step) => {
try { try {
if (step.humanReadableTarget) { if (step.humanReadableTarget) {
this.logger.logConfigChange(step.humanReadableTarget); logger.logConfigChange(step.humanReadableTarget);
} }
if (!_.isObject(step.target)) { if (!_.isObject(step.target)) {
throw new Error('Non-dictionary value passed to changeConfig'); throw new Error('Non-dictionary value passed to changeConfig');
@ -162,7 +155,7 @@ export class DeviceConfig {
// work out and we don't need this cast to any // work out and we don't need this cast to any
await config.set(step.target as { [key in SchemaTypeKey]: any }); await config.set(step.target as { [key in SchemaTypeKey]: any });
if (step.humanReadableTarget) { if (step.humanReadableTarget) {
this.logger.logConfigChange(step.humanReadableTarget, { logger.logConfigChange(step.humanReadableTarget, {
success: true, success: true,
}); });
} }
@ -171,7 +164,7 @@ export class DeviceConfig {
} }
} catch (err) { } catch (err) {
if (step.humanReadableTarget) { if (step.humanReadableTarget) {
this.logger.logConfigChange(step.humanReadableTarget, { logger.logConfigChange(step.humanReadableTarget, {
err, err,
}); });
} }
@ -185,15 +178,15 @@ export class DeviceConfig {
} }
const logValue = { SUPERVISOR_VPN_CONTROL: step.target }; const logValue = { SUPERVISOR_VPN_CONTROL: step.target };
if (!initial) { if (!initial) {
this.logger.logConfigChange(logValue); logger.logConfigChange(logValue);
} }
try { try {
await this.setVPNEnabled(step.target); await this.setVPNEnabled(step.target);
if (!initial) { if (!initial) {
this.logger.logConfigChange(logValue, { success: true }); logger.logConfigChange(logValue, { success: true });
} }
} catch (err) { } catch (err) {
this.logger.logConfigChange(logValue, { err }); logger.logConfigChange(logValue, { err });
throw err; throw err;
} }
}, },
@ -218,9 +211,7 @@ export class DeviceConfig {
} }
const dt = await config.get('deviceType'); const dt = await config.get('deviceType');
this.configBackend = this.configBackend =
(await configUtils.initialiseConfigBackend(dt, { (await configUtils.initialiseConfigBackend(dt)) ?? null;
logger: this.logger,
})) ?? null;
return this.configBackend; return this.configBackend;
} }
@ -350,7 +341,7 @@ export class DeviceConfig {
if (!configBackend!.isSupportedConfig(key)) { if (!configBackend!.isSupportedConfig(key)) {
if (currentBootConfig[key] !== value) { if (currentBootConfig[key] !== value) {
const err = `Attempt to change blacklisted config value ${key}`; const err = `Attempt to change blacklisted config value ${key}`;
this.logger.logSystemMessage( logger.logSystemMessage(
err, err,
{ error: err }, { error: err },
'Apply boot config error', 'Apply boot config error',
@ -419,7 +410,7 @@ export class DeviceConfig {
const message = `Warning: Ignoring invalid device configuration value for ${key}, value: ${inspect( const message = `Warning: Ignoring invalid device configuration value for ${key}, value: ${inspect(
target[envVarName], target[envVarName],
)}. Falling back to default (${defaultValue})`; )}. Falling back to default (${defaultValue})`;
this.logger.logSystemMessage( logger.logSystemMessage(
message, message,
{ key: envVarName, value: target[envVarName] }, { key: envVarName, value: target[envVarName] },
'invalidDeviceConfig', 'invalidDeviceConfig',
@ -532,7 +523,7 @@ export class DeviceConfig {
} }
const conf = configUtils.envToBootConfig(backend, target); const conf = configUtils.envToBootConfig(backend, target);
this.logger.logSystemMessage( logger.logSystemMessage(
`Applying boot config: ${JSON.stringify(conf)}`, `Applying boot config: ${JSON.stringify(conf)}`,
{}, {},
'Apply boot config in progress', 'Apply boot config in progress',
@ -543,7 +534,7 @@ export class DeviceConfig {
try { try {
await backend.setBootConfig(conf); await backend.setBootConfig(conf);
this.logger.logSystemMessage( logger.logSystemMessage(
`Applied boot config: ${JSON.stringify(conf)}`, `Applied boot config: ${JSON.stringify(conf)}`,
{}, {},
'Apply boot config success', 'Apply boot config success',
@ -551,7 +542,7 @@ export class DeviceConfig {
this.rebootRequired = true; this.rebootRequired = true;
return true; return true;
} catch (err) { } catch (err) {
this.logger.logSystemMessage( logger.logSystemMessage(
`Error setting boot config: ${err}`, `Error setting boot config: ${err}`,
{ error: err }, { error: err },
'Apply boot config error', 'Apply boot config error',

View File

@ -10,7 +10,7 @@ import prettyMs = require('pretty-ms');
import * as config from './config'; import * as config from './config';
import * as db from './db'; import * as db from './db';
import Logger from './logger'; import * as logger from './logger';
import { import {
CompositionStep, CompositionStep,
@ -176,7 +176,6 @@ function createDeviceStateRouter(deviceState: DeviceState) {
} }
interface DeviceStateConstructOpts { interface DeviceStateConstructOpts {
logger: Logger;
apiBinder: APIBinder; apiBinder: APIBinder;
} }
@ -216,8 +215,6 @@ type DeviceStateStep<T extends PossibleStepTargets> =
| ConfigStep; | ConfigStep;
export class DeviceState extends (EventEmitter as new () => DeviceStateEventEmitter) { export class DeviceState extends (EventEmitter as new () => DeviceStateEventEmitter) {
public logger: Logger;
public applications: ApplicationManager; public applications: ApplicationManager;
public deviceConfig: DeviceConfig; public deviceConfig: DeviceConfig;
@ -239,14 +236,10 @@ export class DeviceState extends (EventEmitter as new () => DeviceStateEventEmit
public connected: boolean; public connected: boolean;
public router: express.Router; public router: express.Router;
constructor({ logger, apiBinder }: DeviceStateConstructOpts) { constructor({ apiBinder }: DeviceStateConstructOpts) {
super(); super();
this.logger = logger; this.deviceConfig = new DeviceConfig();
this.deviceConfig = new DeviceConfig({
logger: this.logger,
});
this.applications = new ApplicationManager({ this.applications = new ApplicationManager({
logger: this.logger,
deviceState: this, deviceState: this,
apiBinder, apiBinder,
}); });
@ -306,7 +299,7 @@ export class DeviceState extends (EventEmitter as new () => DeviceStateEventEmit
public async init() { public async init() {
config.on('change', (changedConfig) => { config.on('change', (changedConfig) => {
if (changedConfig.loggingEnabled != null) { if (changedConfig.loggingEnabled != null) {
this.logger.enable(changedConfig.loggingEnabled); logger.enable(changedConfig.loggingEnabled);
} }
if (changedConfig.apiSecret != null) { if (changedConfig.apiSecret != null) {
this.reportCurrentState({ api_secret: changedConfig.apiSecret }); this.reportCurrentState({ api_secret: changedConfig.apiSecret });
@ -551,7 +544,7 @@ export class DeviceState extends (EventEmitter as new () => DeviceStateEventEmit
private async reboot(force?: boolean, skipLock?: boolean) { private async reboot(force?: boolean, skipLock?: boolean) {
await this.applications.stopAll({ force, skipLock }); await this.applications.stopAll({ force, skipLock });
this.logger.logSystemMessage('Rebooting', {}, 'Reboot'); logger.logSystemMessage('Rebooting', {}, 'Reboot');
const reboot = await dbus.reboot(); const reboot = await dbus.reboot();
this.shuttingDown = true; this.shuttingDown = true;
this.emitAsync('shutdown', undefined); this.emitAsync('shutdown', undefined);
@ -560,7 +553,7 @@ export class DeviceState extends (EventEmitter as new () => DeviceStateEventEmit
private async shutdown(force?: boolean, skipLock?: boolean) { private async shutdown(force?: boolean, skipLock?: boolean) {
await this.applications.stopAll({ force, skipLock }); await this.applications.stopAll({ force, skipLock });
this.logger.logSystemMessage('Shutting down', {}, 'Shutdown'); logger.logSystemMessage('Shutting down', {}, 'Shutdown');
const shutdown = await dbus.shutdown(); const shutdown = await dbus.shutdown();
this.shuttingDown = true; this.shuttingDown = true;
this.emitAsync('shutdown', undefined); this.emitAsync('shutdown', undefined);
@ -670,7 +663,7 @@ export class DeviceState extends (EventEmitter as new () => DeviceStateEventEmit
const message = `Updates are locked, retrying in ${prettyMs(delay, { const message = `Updates are locked, retrying in ${prettyMs(delay, {
compact: true, compact: true,
})}...`; })}...`;
this.logger.logSystemMessage(message, {}, 'updateLocked', false); logger.logSystemMessage(message, {}, 'updateLocked', false);
log.info(message); log.info(message);
} else { } else {
log.error( log.error(

View File

@ -7,7 +7,7 @@ import * as constants from './lib/constants';
import { docker } from './lib/docker-utils'; import { docker } from './lib/docker-utils';
import { SupervisorContainerNotFoundError } from './lib/errors'; import { SupervisorContainerNotFoundError } from './lib/errors';
import log from './lib/supervisor-console'; import log from './lib/supervisor-console';
import { Logger } from './logger'; import * as logger from './logger';
// EngineSnapshot represents a list of containers, images, volumens, and networks present on the engine. // EngineSnapshot represents a list of containers, images, volumens, and networks present on the engine.
// A snapshot is taken before entering local mode in order to perform cleanup when we exit local mode. // A snapshot is taken before entering local mode in order to perform cleanup when we exit local mode.
@ -71,7 +71,6 @@ const SUPERVISOR_CONTAINER_NAME_FALLBACK = 'resin_supervisor';
*/ */
export class LocalModeManager { export class LocalModeManager {
public constructor( public constructor(
public logger: Logger,
private containerId: string | undefined = constants.containerId, private containerId: string | undefined = constants.containerId,
) {} ) {}
@ -85,7 +84,7 @@ export class LocalModeManager {
const local = changed.localMode || false; const local = changed.localMode || false;
// First switch the logger to it's correct state // First switch the logger to it's correct state
this.logger.switchBackend(local); logger.switchBackend(local);
this.startLocalModeChangeHandling(local); this.startLocalModeChangeHandling(local);
} }

View File

@ -16,51 +16,43 @@ import {
import LogMonitor from './logging/monitor'; import LogMonitor from './logging/monitor';
import * as globalEventBus from './event-bus'; import * as globalEventBus from './event-bus';
import log from './lib/supervisor-console'; import superConsole from './lib/supervisor-console';
interface LoggerSetupOptions {
apiEndpoint: config.ConfigType<'apiEndpoint'>;
uuid: config.ConfigType<'uuid'>;
deviceApiKey: config.ConfigType<'deviceApiKey'>;
unmanaged: config.ConfigType<'unmanaged'>;
localMode: config.ConfigType<'localMode'>;
enableLogs: boolean;
}
type LogEventObject = Dictionary<any> | null; type LogEventObject = Dictionary<any> | null;
export class Logger { // export class Logger {
private backend: LogBackend | null = null; let backend: LogBackend | null = null;
private balenaBackend: BalenaLogBackend | null = null; let balenaBackend: BalenaLogBackend | null = null;
private localBackend: LocalLogBackend | null = null; let localBackend: LocalLogBackend | null = null;
private containerLogs: { [containerId: string]: ContainerLogs } = {}; const containerLogs: { [containerId: string]: ContainerLogs } = {};
private logMonitor: LogMonitor; const logMonitor = new LogMonitor();
public constructor() { export const initialized = (async () => {
this.backend = null; await config.initialized;
this.logMonitor = new LogMonitor(); const {
}
public init({
apiEndpoint, apiEndpoint,
uuid, uuid,
deviceApiKey, deviceApiKey,
unmanaged, unmanaged,
enableLogs, loggingEnabled,
localMode, localMode,
}: LoggerSetupOptions) { } = await config.getMany([
this.balenaBackend = new BalenaLogBackend(apiEndpoint, uuid, deviceApiKey); 'apiEndpoint',
this.localBackend = new LocalLogBackend(); 'uuid',
'deviceApiKey',
'unmanaged',
'loggingEnabled',
'localMode',
]);
this.backend = localMode ? this.localBackend : this.balenaBackend; balenaBackend = new BalenaLogBackend(apiEndpoint, uuid, deviceApiKey);
localBackend = new LocalLogBackend();
backend = localMode ? localBackend : balenaBackend;
backend.unmanaged = unmanaged;
backend.publishEnabled = loggingEnabled;
this.backend.unmanaged = unmanaged; if (!balenaBackend.isInitialised()) {
this.backend.publishEnabled = enableLogs;
// Only setup a config listener if we have to
if (!this.balenaBackend.isIntialised()) {
globalEventBus.getInstance().once('deviceProvisioned', async () => { globalEventBus.getInstance().once('deviceProvisioned', async () => {
const conf = await config.getMany([ const conf = await config.getMany([
'uuid', 'uuid',
@ -75,7 +67,7 @@ export class Logger {
if (_.every(conf, Boolean)) { if (_.every(conf, Boolean)) {
// Everything is set, provide the values to the // Everything is set, provide the values to the
// balenaBackend, and remove our listener // balenaBackend, and remove our listener
this.balenaBackend!.assignFields( balenaBackend!.assignFields(
conf.apiEndpoint, conf.apiEndpoint,
conf.uuid!, conf.uuid!,
conf.deviceApiKey, conf.deviceApiKey,
@ -83,50 +75,50 @@ export class Logger {
} }
}); });
} }
} })();
public switchBackend(localMode: boolean) { export function switchBackend(localMode: boolean) {
if (localMode) { if (localMode) {
// Use the local mode backend // Use the local mode backend
this.backend = this.localBackend; backend = localBackend;
log.info('Switching logging backend to LocalLogBackend'); superConsole.info('Switching logging backend to LocalLogBackend');
} else { } else {
// Use the balena backend // Use the balena backend
this.backend = this.balenaBackend; backend = balenaBackend;
log.info('Switching logging backend to BalenaLogBackend'); superConsole.info('Switching logging backend to BalenaLogBackend');
} }
} }
public getLocalBackend(): LocalLogBackend { export function getLocalBackend(): LocalLogBackend {
// TODO: Think about this interface a little better, it would be // TODO: Think about this interface a little better, it would be
// nicer to proxy the logs via the logger module // nicer to proxy the logs via the logger module
if (this.localBackend == null) { if (localBackend == null) {
// TODO: Type this as an internal inconsistency error // TODO: Type this as an internal inconsistency error
throw new Error('Local backend logger is not defined.'); throw new Error('Local backend logger is not defined.');
} }
return this.localBackend; return localBackend;
} }
public enable(value: boolean = true) { export function enable(value: boolean = true) {
if (this.backend != null) { if (backend != null) {
this.backend.publishEnabled = value; backend.publishEnabled = value;
} }
} }
public logDependent(message: LogMessage, device: { uuid: string }) { export function logDependent(message: LogMessage, device: { uuid: string }) {
if (this.backend != null) { if (backend != null) {
message.uuid = device.uuid; message.uuid = device.uuid;
this.backend.log(message); backend.log(message);
} }
} }
public log(message: LogMessage) { export function log(message: LogMessage) {
if (this.backend != null) { if (backend != null) {
this.backend.log(message); backend.log(message);
} }
} }
public logSystemMessage( export function logSystemMessage(
message: string, message: string,
eventObj?: LogEventObject, eventObj?: LogEventObject,
eventName?: string, eventName?: string,
@ -136,7 +128,7 @@ export class Logger {
if (eventObj != null && eventObj.error != null) { if (eventObj != null && eventObj.error != null) {
msgObj.isStdErr = true; msgObj.isStdErr = true;
} }
this.log(msgObj); log(msgObj);
if (track) { if (track) {
eventTracker.track( eventTracker.track(
eventName != null ? eventName : message, eventName != null ? eventName : message,
@ -145,56 +137,56 @@ export class Logger {
} }
} }
public lock(containerId: string): Bluebird.Disposer<() => void> { export function lock(containerId: string): Bluebird.Disposer<() => void> {
return writeLock(containerId).disposer((release) => { return writeLock(containerId).disposer((release) => {
release(); release();
}); });
} }
public attach( export function attach(
containerId: string, containerId: string,
serviceInfo: { serviceId: number; imageId: number }, serviceInfo: { serviceId: number; imageId: number },
): Bluebird<void> { ): Bluebird<void> {
// First detect if we already have an attached log stream // First detect if we already have an attached log stream
// for this container // for this container
if (containerId in this.containerLogs) { if (containerId in containerLogs) {
return Bluebird.resolve(); return Bluebird.resolve();
} }
return Bluebird.using(this.lock(containerId), async () => { return Bluebird.using(lock(containerId), async () => {
const logs = new ContainerLogs(containerId); const logs = new ContainerLogs(containerId);
this.containerLogs[containerId] = logs; containerLogs[containerId] = logs;
logs.on('error', (err) => { logs.on('error', (err) => {
log.error('Container log retrieval error', err); superConsole.error('Container log retrieval error', err);
delete this.containerLogs[containerId]; delete containerLogs[containerId];
}); });
logs.on('log', async (logMessage) => { logs.on('log', async (logMessage) => {
this.log(_.merge({}, serviceInfo, logMessage)); log(_.merge({}, serviceInfo, logMessage));
// Take the timestamp and set it in the database as the last // Take the timestamp and set it in the database as the last
// log sent for this // log sent for this
this.logMonitor.updateContainerSentTimestamp( logMonitor.updateContainerSentTimestamp(
containerId, containerId,
logMessage.timestamp, logMessage.timestamp,
); );
}); });
logs.on('closed', () => delete this.containerLogs[containerId]); logs.on('closed', () => delete containerLogs[containerId]);
const lastSentTimestamp = await this.logMonitor.getContainerSentTimestamp( const lastSentTimestamp = await logMonitor.getContainerSentTimestamp(
containerId, containerId,
); );
return logs.attach(lastSentTimestamp); return logs.attach(lastSentTimestamp);
}); });
} }
public logSystemEvent( export function logSystemEvent(
logType: LogType, logType: LogType,
obj: LogEventObject, obj: LogEventObject,
track: boolean = true, track: boolean = true,
): void { ): void {
let message = logType.humanName; let message = logType.humanName;
const objectName = this.objectNameForLogs(obj); const objectName = objectNameForLogs(obj);
if (objectName != null) { if (objectName != null) {
message += ` '${objectName}'`; message += ` '${objectName}'`;
} }
@ -203,14 +195,14 @@ export class Logger {
if (_.isEmpty(errorMessage)) { if (_.isEmpty(errorMessage)) {
errorMessage = errorMessage =
obj.error.name !== 'Error' ? obj.error.name : 'Unknown cause'; obj.error.name !== 'Error' ? obj.error.name : 'Unknown cause';
log.warn('Invalid error message', obj.error); superConsole.warn('Invalid error message', obj.error);
} }
message += ` due to '${errorMessage}'`; message += ` due to '${errorMessage}'`;
} }
this.logSystemMessage(message, obj, logType.eventName, track); logSystemMessage(message, obj, logType.eventName, track);
} }
public logConfigChange( export function logConfigChange(
conf: { [configName: string]: string }, conf: { [configName: string]: string },
{ success = false, err }: { success?: boolean; err?: Error } = {}, { success = false, err }: { success?: boolean; err?: Error } = {},
) { ) {
@ -229,18 +221,20 @@ export class Logger {
eventName = 'Apply config change in progress'; eventName = 'Apply config change in progress';
} }
this.logSystemMessage(message, obj, eventName); logSystemMessage(message, obj, eventName);
} }
public async clearOutOfDateDBLogs(containerIds: string[]) { export async function clearOutOfDateDBLogs(containerIds: string[]) {
log.debug('Performing database cleanup for container log timestamps'); superConsole.debug(
'Performing database cleanup for container log timestamps',
);
await db await db
.models('containerLogs') .models('containerLogs')
.whereNotIn('containerId', containerIds) .whereNotIn('containerId', containerIds)
.delete(); .delete();
} }
private objectNameForLogs(eventObj: LogEventObject): string | null { function objectNameForLogs(eventObj: LogEventObject): string | null {
if (eventObj == null) { if (eventObj == null) {
return null; return null;
} }
@ -271,6 +265,3 @@ export class Logger {
return null; return null;
} }
}
export default Logger;

View File

@ -70,7 +70,7 @@ export class BalenaLogBackend extends LogBackend {
}); });
} }
public isIntialised(): boolean { public isInitialised(): boolean {
return this.initialised; return this.initialised;
} }

View File

@ -18,6 +18,7 @@ import { log } from './lib/supervisor-console';
import * as db from './db'; import * as db from './db';
import * as config from './config'; import * as config from './config';
import * as dockerUtils from './lib/docker-utils'; import * as dockerUtils from './lib/docker-utils';
import * as logger from './logger';
const mkdirpAsync = Promise.promisify(mkdirp); const mkdirpAsync = Promise.promisify(mkdirp);
@ -186,7 +187,7 @@ const createProxyvisorRouter = function (proxyvisor) {
if (device.markedForDeletion) { if (device.markedForDeletion) {
return res.status(410).send('Device deleted'); return res.status(410).send('Device deleted');
} }
proxyvisor.logger.logDependent(m, uuid); logger.logDependent(m, { uuid });
return res.status(202).send('OK'); return res.status(202).send('OK');
}) })
.catch(function (err) { .catch(function (err) {
@ -345,7 +346,7 @@ const createProxyvisorRouter = function (proxyvisor) {
}; };
export class Proxyvisor { export class Proxyvisor {
constructor({ logger, images, applications }) { constructor({ images, applications }) {
this.bindToAPI = this.bindToAPI.bind(this); this.bindToAPI = this.bindToAPI.bind(this);
this.executeStepAction = this.executeStepAction.bind(this); this.executeStepAction = this.executeStepAction.bind(this);
this.getCurrentStates = this.getCurrentStates.bind(this); this.getCurrentStates = this.getCurrentStates.bind(this);
@ -361,7 +362,6 @@ export class Proxyvisor {
this.sendUpdate = this.sendUpdate.bind(this); this.sendUpdate = this.sendUpdate.bind(this);
this.sendDeleteHook = this.sendDeleteHook.bind(this); this.sendDeleteHook = this.sendDeleteHook.bind(this);
this.sendUpdates = this.sendUpdates.bind(this); this.sendUpdates = this.sendUpdates.bind(this);
this.logger = logger;
this.images = images; this.images = images;
this.applications = applications; this.applications = applications;
this.acknowledgedState = {}; this.acknowledgedState = {};

View File

@ -6,7 +6,7 @@ import * as eventTracker from './event-tracker';
import { intialiseContractRequirements } from './lib/contracts'; import { intialiseContractRequirements } from './lib/contracts';
import { normaliseLegacyDatabase } from './lib/migration'; import { normaliseLegacyDatabase } from './lib/migration';
import * as osRelease from './lib/os-release'; import * as osRelease from './lib/os-release';
import Logger from './logger'; import * as logger from './logger';
import SupervisorAPI from './supervisor-api'; import SupervisorAPI from './supervisor-api';
import constants = require('./lib/constants'); import constants = require('./lib/constants');
@ -29,18 +29,13 @@ const startupConfigFields: config.ConfigKey[] = [
]; ];
export class Supervisor { export class Supervisor {
private logger: Logger;
private deviceState: DeviceState; private deviceState: DeviceState;
private apiBinder: APIBinder; private apiBinder: APIBinder;
private api: SupervisorAPI; private api: SupervisorAPI;
public constructor() { public constructor() {
this.logger = new Logger(); this.apiBinder = new APIBinder();
this.apiBinder = new APIBinder({
logger: this.logger,
});
this.deviceState = new DeviceState({ this.deviceState = new DeviceState({
logger: this.logger,
apiBinder: this.apiBinder, apiBinder: this.apiBinder,
}); });
// workaround the circular dependency // workaround the circular dependency
@ -65,15 +60,11 @@ export class Supervisor {
await db.initialized; await db.initialized;
await config.initialized; await config.initialized;
await eventTracker.initialized; await eventTracker.initialized;
log.debug('Starting logging infrastructure');
await logger.initialized;
const conf = await config.getMany(startupConfigFields); const conf = await config.getMany(startupConfigFields);
log.debug('Starting logging infrastructure');
this.logger.init({
enableLogs: conf.loggingEnabled,
...conf,
});
intialiseContractRequirements({ intialiseContractRequirements({
supervisorVersion: version, supervisorVersion: version,
deviceType: await config.get('deviceType'), deviceType: await config.get('deviceType'),
@ -83,7 +74,7 @@ export class Supervisor {
log.debug('Starting api binder'); log.debug('Starting api binder');
await this.apiBinder.initClient(); await this.apiBinder.initClient();
this.logger.logSystemMessage('Supervisor starting', {}, 'Supervisor start'); logger.logSystemMessage('Supervisor starting', {}, 'Supervisor start');
if (conf.legacyAppsPresent && this.apiBinder.balenaApi != null) { if (conf.legacyAppsPresent && this.apiBinder.balenaApi != null) {
log.info('Legacy app detected, running migration'); log.info('Legacy app detected, running migration');
await normaliseLegacyDatabase( await normaliseLegacyDatabase(

View File

@ -208,11 +208,6 @@ const testTargetInvalid = {
}; };
describe('deviceState', () => { describe('deviceState', () => {
const logger = {
clearOutOfDateDBLogs() {
/* noop */
},
};
let deviceState: DeviceState; let deviceState: DeviceState;
before(async () => { before(async () => {
await prepare(); await prepare();
@ -228,7 +223,6 @@ describe('deviceState', () => {
}); });
deviceState = new DeviceState({ deviceState = new DeviceState({
logger: logger as any,
apiBinder: null as any, apiBinder: null as any,
}); });

View File

@ -35,12 +35,9 @@ const initModels = async (obj: Dictionary<any>, filename: string) => {
}, },
} as any; } as any;
obj.apiBinder = new ApiBinder({ obj.apiBinder = new ApiBinder();
logger: obj.logger,
});
obj.deviceState = new DeviceState({ obj.deviceState = new DeviceState({
logger: obj.logger,
apiBinder: obj.apiBinder, apiBinder: obj.apiBinder,
}); });

View File

@ -5,13 +5,14 @@ import * as Promise from 'bluebird';
import { expect } from './lib/chai-config'; import { expect } from './lib/chai-config';
import * as sinon from 'sinon'; import * as sinon from 'sinon';
import { Logger } from '../src/logger';
import { ContainerLogs } from '../src/logging/container'; import { ContainerLogs } from '../src/logging/container';
import * as eventTracker from '../src/event-tracker'; import * as config from '../src/config';
import { stub } from 'sinon';
describe('Logger', function () { describe('Logger', function () {
beforeEach(function () { let logger: typeof import('../src/logger');
let configStub: sinon.SinonStub;
beforeEach(async function () {
this._req = new stream.PassThrough(); this._req = new stream.PassThrough();
this._req.flushHeaders = sinon.spy(); this._req.flushHeaders = sinon.spy();
this._req.end = sinon.spy(); this._req.end = sinon.spy();
@ -23,27 +24,35 @@ describe('Logger', function () {
this.requestStub = sinon.stub(https, 'request').returns(this._req); this.requestStub = sinon.stub(https, 'request').returns(this._req);
this.eventTrackerStub = stub(eventTracker, 'track'); configStub = sinon.stub(config, 'getMany').returns(
// @ts-ignore this should actually work but the type system doesnt like it
this.logger = new Logger(); Promise.resolve({
return this.logger.init({
apiEndpoint: 'https://example.com', apiEndpoint: 'https://example.com',
uuid: 'deadbeef', uuid: 'deadbeef',
deviceApiKey: 'secretkey', deviceApiKey: 'secretkey',
unmanaged: false, unmanaged: false,
enableLogs: true, loggingEnabled: true,
localMode: false, localMode: false,
}); }),
);
// delete the require cache for the logger module so we can force a refresh
delete require.cache[require.resolve('../src/logger')];
logger = await import('../src/logger');
await logger.initialized;
}); });
afterEach(function () { afterEach(function () {
this.requestStub.restore(); this.requestStub.restore();
this.eventTrackerStub.restore(); configStub.restore();
});
after(function () {
delete require.cache[require.resolve('../src/logger')];
}); });
it('waits the grace period before sending any logs', function () { it('waits the grace period before sending any logs', function () {
const clock = sinon.useFakeTimers(); const clock = sinon.useFakeTimers();
this.logger.log({ message: 'foobar', serviceId: 15 }); logger.log({ message: 'foobar', serviceId: 15 });
clock.tick(4999); clock.tick(4999);
clock.restore(); clock.restore();
@ -54,7 +63,7 @@ describe('Logger', function () {
it('tears down the connection after inactivity', function () { it('tears down the connection after inactivity', function () {
const clock = sinon.useFakeTimers(); const clock = sinon.useFakeTimers();
this.logger.log({ message: 'foobar', serviceId: 15 }); logger.log({ message: 'foobar', serviceId: 15 });
clock.tick(61000); clock.tick(61000);
clock.restore(); clock.restore();
@ -65,9 +74,9 @@ describe('Logger', function () {
it('sends logs as gzipped ndjson', function () { it('sends logs as gzipped ndjson', function () {
const timestamp = Date.now(); const timestamp = Date.now();
this.logger.log({ message: 'foobar', serviceId: 15 }); logger.log({ message: 'foobar', serviceId: 15 });
this.logger.log({ timestamp: 1337, message: 'foobar', serviceId: 15 }); logger.log({ timestamp: 1337, message: 'foobar', serviceId: 15 });
this.logger.log({ message: 'foobar' }); // shold be ignored logger.log({ message: 'foobar' }); // shold be ignored
return Promise.delay(5500).then(() => { return Promise.delay(5500).then(() => {
expect(this.requestStub.calledOnce).to.be.true; expect(this.requestStub.calledOnce).to.be.true;
@ -102,16 +111,13 @@ describe('Logger', function () {
it('allows logging system messages which are also reported to the eventTracker', function () { it('allows logging system messages which are also reported to the eventTracker', function () {
const timestamp = Date.now(); const timestamp = Date.now();
this.logger.logSystemMessage( logger.logSystemMessage(
'Hello there!', 'Hello there!',
{ someProp: 'someVal' }, { someProp: 'someVal' },
'Some event name', 'Some event name',
); );
return Promise.delay(5500).then(() => { return Promise.delay(5500).then(() => {
expect(this.eventTrackerStub).to.be.calledWith('Some event name', {
someProp: 'someVal',
});
const lines = this._req.body.split('\n'); const lines = this._req.body.split('\n');
expect(lines.length).to.equal(2); expect(lines.length).to.equal(2);
expect(lines[1]).to.equal(''); expect(lines[1]).to.equal('');

View File

@ -1,7 +1,7 @@
import { Promise } from 'bluebird'; import { Promise } from 'bluebird';
import { stripIndent } from 'common-tags'; import { stripIndent } from 'common-tags';
import { child_process, fs } from 'mz'; import { child_process, fs } from 'mz';
import { SinonSpy, SinonStub, spy, stub } from 'sinon'; import { SinonSpy, SinonStub, stub } from 'sinon';
import * as config from '../src/config'; import * as config from '../src/config';
import { ExtlinuxConfigBackend, RPiConfigBackend } from '../src/config/backend'; import { ExtlinuxConfigBackend, RPiConfigBackend } from '../src/config/backend';
@ -9,6 +9,8 @@ import { DeviceConfig } from '../src/device-config';
import * as fsUtils from '../src/lib/fs-utils'; import * as fsUtils from '../src/lib/fs-utils';
import { expect } from './lib/chai-config'; import { expect } from './lib/chai-config';
import * as logger from '../src/logger';
import prepare = require('./lib/prepare'); import prepare = require('./lib/prepare');
const extlinuxBackend = new ExtlinuxConfigBackend(); const extlinuxBackend = new ExtlinuxConfigBackend();
@ -28,12 +30,12 @@ describe('DeviceConfig', function () {
}); });
}, },
}; };
this.fakeLogger = { this.logStub = stub(logger, 'logSystemMessage');
logSystemMessage: spy(), return (this.deviceConfig = new DeviceConfig());
}; });
return (this.deviceConfig = new DeviceConfig({
logger: this.fakeLogger, after(function () {
})); this.logStub.restore();
}); });
// Test that the format for special values like initramfs and array variables is parsed correctly // Test that the format for special values like initramfs and array variables is parsed correctly
@ -97,15 +99,15 @@ describe('DeviceConfig', function () {
}); });
expect(promise).to.be.rejected; expect(promise).to.be.rejected;
return promise.catch((_err) => { return promise.catch((_err) => {
expect(this.fakeLogger.logSystemMessage).to.be.calledOnce; expect(this.logStub).to.be.calledOnce;
expect(this.fakeLogger.logSystemMessage).to.be.calledWith( expect(this.logStub).to.be.calledWith(
'Attempt to change blacklisted config value initramfs', 'Attempt to change blacklisted config value initramfs',
{ {
error: 'Attempt to change blacklisted config value initramfs', error: 'Attempt to change blacklisted config value initramfs',
}, },
'Apply boot config error', 'Apply boot config error',
); );
return this.fakeLogger.logSystemMessage.resetHistory(); return this.logStub.resetHistory();
}); });
}); });
@ -133,8 +135,8 @@ describe('DeviceConfig', function () {
}); });
expect(promise).to.eventually.equal(false); expect(promise).to.eventually.equal(false);
return promise.then(() => { return promise.then(() => {
expect(this.fakeLogger.logSystemMessage).to.not.be.called; expect(this.logStub).to.not.be.called;
return this.fakeLogger.logSystemMessage.resetHistory(); return this.logStub.resetHistory();
}); });
}); });
@ -168,8 +170,8 @@ describe('DeviceConfig', function () {
.setBootConfig(rpiConfigBackend, target) .setBootConfig(rpiConfigBackend, target)
.then(() => { .then(() => {
expect(child_process.exec).to.be.calledOnce; expect(child_process.exec).to.be.calledOnce;
expect(this.fakeLogger.logSystemMessage).to.be.calledTwice; expect(this.logStub).to.be.calledTwice;
expect(this.fakeLogger.logSystemMessage.getCall(1).args[2]).to.equal( expect(this.logStub.getCall(1).args[2]).to.equal(
'Apply boot config success', 'Apply boot config success',
); );
expect(fsUtils.writeFileAtomic).to.be.calledWith( expect(fsUtils.writeFileAtomic).to.be.calledWith(
@ -185,7 +187,7 @@ foobaz=bar\n\
); );
(fsUtils.writeFileAtomic as SinonStub).restore(); (fsUtils.writeFileAtomic as SinonStub).restore();
(child_process.exec as SinonStub).restore(); (child_process.exec as SinonStub).restore();
return this.fakeLogger.logSystemMessage.resetHistory(); return this.logStub.resetHistory();
}); });
}); });
}); });
@ -254,10 +256,10 @@ foobaz=bar\n\
.setBootConfig(extlinuxBackend, target) .setBootConfig(extlinuxBackend, target)
.then(() => { .then(() => {
expect(child_process.exec).to.be.calledOnce; expect(child_process.exec).to.be.calledOnce;
expect(this.fakeLogger.logSystemMessage).to.be.calledTwice; expect(this.logStub).to.be.calledTwice;
expect( expect(this.logStub.getCall(1).args[2]).to.equal(
this.fakeLogger.logSystemMessage.getCall(1).args[2], 'Apply boot config success',
).to.equal('Apply boot config success'); );
expect(fsUtils.writeFileAtomic).to.be.calledWith( expect(fsUtils.writeFileAtomic).to.be.calledWith(
'./test/data/mnt/boot/extlinux/extlinux.conf', './test/data/mnt/boot/extlinux/extlinux.conf',
`\ `\
@ -272,7 +274,7 @@ APPEND \${cbootargs} \${resin_kernel_root} ro rootwait isolcpus=2\n\
); );
(fsUtils.writeFileAtomic as SinonStub).restore(); (fsUtils.writeFileAtomic as SinonStub).restore();
(child_process.exec as SinonStub).restore(); (child_process.exec as SinonStub).restore();
return this.fakeLogger.logSystemMessage.resetHistory(); return this.logStub.resetHistory();
}); });
}); });
})); }));
@ -405,9 +407,7 @@ APPEND \${cbootargs} \${resin_kernel_root} ro rootwait isolcpus=2\n\
throw new Error('Unknown fake config key'); throw new Error('Unknown fake config key');
}); });
}); });
this.upboardConfig = new DeviceConfig({ this.upboardConfig = new DeviceConfig();
logger: this.fakeLogger,
});
stub(child_process, 'exec').resolves(); stub(child_process, 'exec').resolves();
stub(fs, 'exists').callsFake(() => Promise.resolve(true)); stub(fs, 'exists').callsFake(() => Promise.resolve(true));
@ -445,7 +445,7 @@ APPEND \${cbootargs} \${resin_kernel_root} ro rootwait isolcpus=2\n\
(fs.readFile as SinonStub).restore(); (fs.readFile as SinonStub).restore();
(fsUtils.writeFileAtomic as SinonStub).restore(); (fsUtils.writeFileAtomic as SinonStub).restore();
(config.get as SinonStub).restore(); (config.get as SinonStub).restore();
this.fakeLogger.logSystemMessage.resetHistory(); this.logStub.resetHistory();
}); });
it('should correctly load the configfs.json file', function () { it('should correctly load the configfs.json file', function () {
@ -464,7 +464,7 @@ APPEND \${cbootargs} \${resin_kernel_root} ro rootwait isolcpus=2\n\
HOST_CONFIGFS_ssdt: 'spidev1,1', HOST_CONFIGFS_ssdt: 'spidev1,1',
}; };
this.fakeLogger.logSystemMessage.resetHistory(); this.logStub.resetHistory();
(child_process.exec as SinonSpy).resetHistory(); (child_process.exec as SinonSpy).resetHistory();
(fs.exists as SinonSpy).resetHistory(); (fs.exists as SinonSpy).resetHistory();
(fs.mkdir as SinonSpy).resetHistory(); (fs.mkdir as SinonSpy).resetHistory();
@ -493,10 +493,10 @@ APPEND \${cbootargs} \${resin_kernel_root} ro rootwait isolcpus=2\n\
ssdt: ['spidev1,1'], ssdt: ['spidev1,1'],
}), }),
); );
expect(this.fakeLogger.logSystemMessage).to.be.calledTwice; expect(this.logStub).to.be.calledTwice;
return expect( return expect(this.logStub.getCall(1).args[2]).to.equal(
this.fakeLogger.logSystemMessage.getCall(1).args[2], 'Apply boot config success',
).to.equal('Apply boot config success'); );
}); });
}); });
}); });

View File

@ -125,13 +125,7 @@ const dependentDBFormat = {
describe('ApplicationManager', function () { describe('ApplicationManager', function () {
before(async function () { before(async function () {
await prepare(); await prepare();
this.logger = {
clearOutOfDateDBLogs: () => {
/* noop */
},
} as any;
this.deviceState = new DeviceState({ this.deviceState = new DeviceState({
logger: this.logger,
apiBinder: null as any, apiBinder: null as any,
}); });
this.applications = this.deviceState.applications; this.applications = this.deviceState.applications;
@ -171,9 +165,7 @@ describe('ApplicationManager', function () {
appCloned.networks = _.mapValues( appCloned.networks = _.mapValues(
appCloned.networks, appCloned.networks,
(config, name) => { (config, name) => {
return Network.fromComposeObject(name, app.appId, config, { return Network.fromComposeObject(name, app.appId, config);
logger: this.logger,
});
}, },
); );
return appCloned; return appCloned;
@ -639,10 +631,7 @@ describe('ApplicationManager', function () {
Bluebird.Promise.resolve([ Bluebird.Promise.resolve([
{ {
action: 'removeVolume', action: 'removeVolume',
current: Volume.fromComposeObject('my_volume', 12, {}, { current: Volume.fromComposeObject('my_volume', 12, {}),
docker: null,
logger: null,
} as any),
}, },
]), ]),
); );

View File

@ -2,29 +2,29 @@ import { expect } from 'chai';
import { stub, SinonStub } from 'sinon'; import { stub, SinonStub } from 'sinon';
import { docker } from '../src/lib/docker-utils'; import { docker } from '../src/lib/docker-utils';
import * as logger from '../src/logger';
import Volume from '../src/compose/volume'; import Volume from '../src/compose/volume';
import logTypes = require('../src/lib/log-types'); import logTypes = require('../src/lib/log-types');
const fakeLogger = {
logSystemMessage: stub(),
logSystemEvent: stub(),
};
const opts: any = { logger: fakeLogger };
describe('Compose volumes', () => { describe('Compose volumes', () => {
let createVolumeStub: SinonStub; let createVolumeStub: SinonStub;
let logSystemStub: SinonStub;
let logMessageStub: SinonStub;
before(() => { before(() => {
createVolumeStub = stub(docker, 'createVolume'); createVolumeStub = stub(docker, 'createVolume');
logSystemStub = stub(logger, 'logSystemEvent');
logMessageStub = stub(logger, 'logSystemMessage');
}); });
after(() => { after(() => {
createVolumeStub.restore(); createVolumeStub.restore();
logSystemStub.restore();
logMessageStub.restore();
}); });
describe('Parsing volumes', () => { describe('Parsing volumes', () => {
it('should correctly parse docker volumes', () => { it('should correctly parse docker volumes', () => {
const volume = Volume.fromDockerVolume(opts, { const volume = Volume.fromDockerVolume({
Driver: 'local', Driver: 'local',
Labels: { Labels: {
'io.balena.supervised': 'true', 'io.balena.supervised': 'true',
@ -54,19 +54,14 @@ describe('Compose volumes', () => {
}); });
it('should correctly parse compose volumes without an explicit driver', () => { it('should correctly parse compose volumes without an explicit driver', () => {
const volume = Volume.fromComposeObject( const volume = Volume.fromComposeObject('one_volume', 1032480, {
'one_volume',
1032480,
{
driver_opts: { driver_opts: {
opt1: 'test', opt1: 'test',
}, },
labels: { labels: {
'my-label': 'test-label', 'my-label': 'test-label',
}, },
}, });
opts,
);
expect(volume).to.have.property('appId').that.equals(1032480); expect(volume).to.have.property('appId').that.equals(1032480);
expect(volume).to.have.property('name').that.equals('one_volume'); expect(volume).to.have.property('name').that.equals('one_volume');
@ -90,10 +85,7 @@ describe('Compose volumes', () => {
}); });
it('should correctly parse compose volumes with an explicit driver', () => { it('should correctly parse compose volumes with an explicit driver', () => {
const volume = Volume.fromComposeObject( const volume = Volume.fromComposeObject('one_volume', 1032480, {
'one_volume',
1032480,
{
driver: 'other', driver: 'other',
driver_opts: { driver_opts: {
opt1: 'test', opt1: 'test',
@ -101,9 +93,7 @@ describe('Compose volumes', () => {
labels: { labels: {
'my-label': 'test-label', 'my-label': 'test-label',
}, },
}, });
opts,
);
expect(volume).to.have.property('appId').that.equals(1032480); expect(volume).to.have.property('appId').that.equals(1032480);
expect(volume).to.have.property('name').that.equals('one_volume'); expect(volume).to.have.property('name').that.equals('one_volume');
@ -130,23 +120,18 @@ describe('Compose volumes', () => {
describe('Generating docker options', () => { describe('Generating docker options', () => {
afterEach(() => { afterEach(() => {
createVolumeStub.reset(); createVolumeStub.reset();
fakeLogger.logSystemEvent.reset(); logSystemStub.reset();
fakeLogger.logSystemMessage.reset(); logMessageStub.reset();
}); });
it('should correctly generate docker options', async () => { it('should correctly generate docker options', async () => {
const volume = Volume.fromComposeObject( const volume = Volume.fromComposeObject('one_volume', 1032480, {
'one_volume',
1032480,
{
driver_opts: { driver_opts: {
opt1: 'test', opt1: 'test',
}, },
labels: { labels: {
'my-label': 'test-label', 'my-label': 'test-label',
}, },
}, });
opts,
);
await volume.create(); await volume.create();
expect( expect(
@ -161,7 +146,7 @@ describe('Compose volumes', () => {
}), }),
); );
expect(fakeLogger.logSystemEvent.calledWith(logTypes.createVolume)); expect(logSystemStub.calledWith(logTypes.createVolume));
}); });
}); });
}); });

View File

@ -9,7 +9,6 @@ import LocalModeManager, {
EngineSnapshot, EngineSnapshot,
EngineSnapshotRecord, EngineSnapshotRecord,
} from '../src/local-mode'; } from '../src/local-mode';
import Logger from '../src/logger';
import ShortStackError from './lib/errors'; import ShortStackError from './lib/errors';
describe('LocalModeManager', () => { describe('LocalModeManager', () => {
@ -34,9 +33,8 @@ describe('LocalModeManager', () => {
await db.initialized; await db.initialized;
dockerStub = sinon.stub(docker); dockerStub = sinon.stub(docker);
const loggerStub = (sinon.createStubInstance(Logger) as unknown) as Logger;
localMode = new LocalModeManager(loggerStub, supervisorContainerId); localMode = new LocalModeManager(supervisorContainerId);
}); });
after(async () => { after(async () => {

View File

@ -72,7 +72,6 @@ async function create(): Promise<SupervisorAPI> {
setupStubs(); setupStubs();
// Create ApplicationManager // Create ApplicationManager
const appManager = new ApplicationManager({ const appManager = new ApplicationManager({
logger: null,
deviceState, deviceState,
apiBinder: null, apiBinder: null,
}); });
@ -102,12 +101,9 @@ async function createAPIOpts(): Promise<SupervisorAPIOpts> {
await initConfig(); await initConfig();
// Create deviceState // Create deviceState
const deviceState = new DeviceState({ const deviceState = new DeviceState({
logger: null as any,
apiBinder: null as any, apiBinder: null as any,
}); });
const apiBinder = new APIBinder({ const apiBinder = new APIBinder();
logger: null as any,
});
return { return {
deviceState, deviceState,
apiBinder, apiBinder,