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 * as globalEventBus from './event-bus';
import Logger from './logger';
import * as TargetState from './device-state/target-state';
import * as logger from './logger';
// The exponential backoff starts at 15s
const MINIMUM_BACKOFF_DELAY = 15000;
@ -40,10 +40,6 @@ const INTERNAL_STATE_KEYS = [
'update_failed',
];
export interface APIBinderConstructOpts {
logger: Logger;
}
interface Device {
id: number;
@ -67,7 +63,6 @@ export class APIBinder {
public router: express.Router;
private deviceState: DeviceState;
private logger: Logger;
public balenaApi: PinejsClientRequest | null = null;
private lastReportedState: DeviceStatus = {
@ -82,9 +77,7 @@ export class APIBinder {
private stateReportErrors = 0;
private readyForUpdates = false;
public constructor({ logger }: APIBinderConstructOpts) {
this.logger = logger;
public constructor() {
this.router = this.createAPIBinderRouter(this);
}
@ -241,12 +234,7 @@ export class APIBinder {
const lines = err.message.split(/\r?\n/);
lines[0] = `Could not move to new release: ${lines[0]}`;
for (const line of lines) {
this.logger.logSystemMessage(
line,
{},
'targetStateRejection',
false,
);
logger.logSystemMessage(line, {}, 'targetStateRejection', false);
}
} else {
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 { ServiceAction } from './device-api/common';
import { Logger } from './logger';
import { DeviceStatus, InstancedAppState } from './types/state';
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
// typecript, type the following
public _lockingIfNecessary: any;
public logger: Logger;
public deviceState: DeviceState;
public apiBinder: APIBinder;
@ -63,11 +61,7 @@ class ApplicationManager extends EventEmitter {
public router: Router;
public constructor({
logger: Logger,
deviceState: DeviceState,
apiBinder: APIBinder,
});
public constructor({ deviceState: DeviceState, apiBinder: APIBinder });
public init(): Promise<void>;

View File

@ -9,6 +9,7 @@ import * as path from 'path';
import * as constants from './lib/constants';
import { log } from './lib/supervisor-console';
import * as config from './config';
import * as logger from './logger';
import { validateTargetContracts } from './lib/contracts';
import { docker } from './lib/docker-utils';
@ -79,7 +80,7 @@ const createApplicationManagerRouter = function (applications) {
};
export class ApplicationManager extends EventEmitter {
constructor({ logger, deviceState, apiBinder }) {
constructor({ deviceState, apiBinder }) {
super();
this.serviceAction = serviceAction;
@ -169,27 +170,17 @@ export class ApplicationManager extends EventEmitter {
this.removeAllVolumesForApp = this.removeAllVolumesForApp.bind(this);
this.localModeSwitchCompletion = this.localModeSwitchCompletion.bind(this);
this.reportOptionalContainers = this.reportOptionalContainers.bind(this);
this.logger = logger;
this.deviceState = deviceState;
this.apiBinder = apiBinder;
this.images = new Images({
logger: this.logger,
});
this.services = new ServiceManager({
logger: this.logger,
});
this.networks = new NetworkManager({
logger: this.logger,
});
this.volumes = new VolumeManager({
logger: this.logger,
});
this.images = new Images();
this.services = new ServiceManager();
this.networks = new NetworkManager();
this.volumes = new VolumeManager();
this.proxyvisor = new Proxyvisor({
logger: this.logger,
images: this.images,
applications: this,
});
this.localModeManager = new LocalModeManager(this.logger);
this.localModeManager = new LocalModeManager();
this.timeSpentFetching = 0;
this.fetchesInProgress = 0;
this._targetVolatilePerImageId = {};
@ -252,7 +243,7 @@ export class ApplicationManager extends EventEmitter {
.then(() => {
const cleanup = () => {
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
@ -1052,15 +1043,11 @@ export class ApplicationManager extends EventEmitter {
}
createTargetVolume(name, appId, volume) {
return Volume.fromComposeObject(name, appId, volume, {
logger: this.logger,
});
return Volume.fromComposeObject(name, appId, volume);
}
createTargetNetwork(name, appId, network) {
return Network.fromComposeObject(name, appId, network, {
logger: this.logger,
});
return Network.fromComposeObject(name, appId, network);
}
normaliseAndExtendAppFromDB(app) {
@ -1702,7 +1689,7 @@ export class ApplicationManager extends EventEmitter {
'. ',
)}`;
log.info(message);
return this.logger.logSystemMessage(
return logger.logSystemMessage(
message,
{},
'optionalContainerViolation',

View File

@ -16,7 +16,7 @@ import * as dockerUtils from '../lib/docker-utils';
import { DeltaStillProcessingError, NotFoundError } from '../lib/errors';
import * as LogTypes from '../lib/log-types';
import * as validation from '../lib/validation';
import Logger from '../logger';
import * as logger from '../logger';
import { ImageDownloadBackoffError } from './errors';
import log from '../lib/supervisor-console';
@ -27,10 +27,6 @@ interface ImageEvents {
type ImageEventEmitter = StrictEventEmitter<EventEmitter, ImageEvents>;
interface ImageConstructOpts {
logger: Logger;
}
interface FetchProgressEvent {
percentage: number;
}
@ -57,8 +53,6 @@ type NormalisedDockerImage = Docker.ImageInfo & {
};
export class Images extends (EventEmitter as new () => ImageEventEmitter) {
private logger: Logger;
public appUpdatePollInterval: 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
private volatileState: { [imageId: number]: Image } = {};
public constructor(opts: ImageConstructOpts) {
public constructor() {
super();
this.logger = opts.logger;
}
public async triggerFetch(
@ -143,7 +135,7 @@ export class Images extends (EventEmitter as new () => ImageEventEmitter) {
await db.models('image').update({ dockerImageId: id }).where(image);
this.logger.logSystemEvent(LogTypes.downloadImageSuccess, { image });
logger.logSystemEvent(LogTypes.downloadImageSuccess, { image });
success = true;
delete this.imageFetchFailures[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,
// don't show a failure message, and instead just inform the user that it's remotely
// processing
this.logger.logSystemEvent(LogTypes.deltaStillProcessingError, {});
logger.logSystemEvent(LogTypes.deltaStillProcessingError, {});
} else {
this.addImageFailure(image.name);
this.logger.logSystemEvent(LogTypes.downloadImageError, {
logger.logSystemEvent(LogTypes.downloadImageError, {
image,
error: err,
});
@ -173,7 +165,7 @@ export class Images extends (EventEmitter as new () => ImageEventEmitter) {
try {
await this.removeImageIfNotNeeded(image);
} catch (e) {
this.logger.logSystemEvent(LogTypes.deleteImageError, {
logger.logSystemEvent(LogTypes.deleteImageError, {
image,
error: e,
});
@ -439,7 +431,7 @@ export class Images extends (EventEmitter as new () => ImageEventEmitter) {
await docker.getImage(image).remove({ force: true });
delete this.imageCleanupFailures[image];
} catch (e) {
this.logger.logSystemMessage(
logger.logSystemMessage(
`Error cleaning up ${image}: ${e.message} - will ignore for 1 hour`,
{ error: e },
'Image cleanup error',
@ -512,7 +504,7 @@ export class Images extends (EventEmitter as new () => ImageEventEmitter) {
image.imageId,
_.merge(_.clone(image), { status: 'Deleting' }),
);
this.logger.logSystemEvent(LogTypes.deleteImage, { image });
logger.logSystemEvent(LogTypes.deleteImage, { image });
docker.getImage(img.dockerImageId).remove({ force: true });
removed = true;
} 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 });
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,
serviceName: string,
): Promise<string> {
this.logger.logSystemEvent(LogTypes.downloadImageDelta, { image });
logger.logSystemEvent(LogTypes.downloadImageDelta, { image });
const deltaOpts = (opts as unknown) as DeltaFetchOptions;
const srcImage = await this.inspectByName(deltaOpts.deltaSource);
@ -610,7 +602,7 @@ export class Images extends (EventEmitter as new () => ImageEventEmitter) {
opts: FetchOptions,
onProgress: (evt: FetchProgressEvent) => void,
): Promise<string> {
this.logger.logSystemEvent(LogTypes.downloadImage, { image });
logger.logSystemEvent(LogTypes.downloadImage, { image });
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 { ENOENT, NotFoundError } from '../lib/errors';
import logTypes = require('../lib/log-types');
import { Logger } from '../logger';
import { Network, NetworkOptions } from './network';
import * as logger from '../logger';
import { Network } from './network';
import log from '../lib/supervisor-console';
import { ResourceRecreationAttemptError } from './errors';
export class NetworkManager {
private logger: Logger;
constructor(opts: NetworkOptions) {
this.logger = opts.logger;
}
public getAll(): Bluebird<Network[]> {
return this.getWithBothLabels().map((network: { Name: string }) => {
return docker
.getNetwork(network.Name)
.inspect()
.then((net) => {
return Network.fromDockerNetwork(
{
logger: this.logger,
},
net,
);
return Network.fromDockerNetwork(net);
});
});
}
@ -43,7 +32,7 @@ export class NetworkManager {
const dockerNet = await docker
.getNetwork(Network.generateDockerName(network.appId, network.name))
.inspect();
return Network.fromDockerNetwork({ logger: this.logger }, dockerNet);
return Network.fromDockerNetwork(dockerNet);
}
public async create(network: Network) {
@ -60,7 +49,7 @@ export class NetworkManager {
// already created, we can skip this
} catch (e) {
if (!NotFoundError(e)) {
this.logger.logSystemEvent(logTypes.createNetworkError, {
logger.logSystemEvent(logTypes.createNetworkError, {
network: { name: network.name, appId: network.appId },
error: e,
});

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -7,6 +7,7 @@ import { Service } from '../compose/service';
import Volume from '../compose/volume';
import * as config from '../config';
import * as db from '../db';
import * as logger from '../logger';
import { spawnJournalctl } from '../lib/journald';
import {
appNotFoundMessage,
@ -323,7 +324,7 @@ export function createV2Api(router: Router, applications: ApplicationManager) {
router.get('/v2/local/logs', async (_req, res) => {
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
backend.assignServiceNameResolver(async (id: number) => {
if (id in serviceNameCache) {

View File

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

View File

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

View File

@ -7,7 +7,7 @@ import * as constants from './lib/constants';
import { docker } from './lib/docker-utils';
import { SupervisorContainerNotFoundError } from './lib/errors';
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.
// 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 {
public constructor(
public logger: Logger,
private containerId: string | undefined = constants.containerId,
) {}
@ -85,7 +84,7 @@ export class LocalModeManager {
const local = changed.localMode || false;
// First switch the logger to it's correct state
this.logger.switchBackend(local);
logger.switchBackend(local);
this.startLocalModeChangeHandling(local);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -5,13 +5,14 @@ import * as Promise from 'bluebird';
import { expect } from './lib/chai-config';
import * as sinon from 'sinon';
import { Logger } from '../src/logger';
import { ContainerLogs } from '../src/logging/container';
import * as eventTracker from '../src/event-tracker';
import { stub } from 'sinon';
import * as config from '../src/config';
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.flushHeaders = sinon.spy();
this._req.end = sinon.spy();
@ -23,27 +24,35 @@ describe('Logger', function () {
this.requestStub = sinon.stub(https, 'request').returns(this._req);
this.eventTrackerStub = stub(eventTracker, 'track');
this.logger = new Logger();
return this.logger.init({
configStub = sinon.stub(config, 'getMany').returns(
// @ts-ignore this should actually work but the type system doesnt like it
Promise.resolve({
apiEndpoint: 'https://example.com',
uuid: 'deadbeef',
deviceApiKey: 'secretkey',
unmanaged: false,
enableLogs: true,
loggingEnabled: true,
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 () {
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 () {
const clock = sinon.useFakeTimers();
this.logger.log({ message: 'foobar', serviceId: 15 });
logger.log({ message: 'foobar', serviceId: 15 });
clock.tick(4999);
clock.restore();
@ -54,7 +63,7 @@ describe('Logger', function () {
it('tears down the connection after inactivity', function () {
const clock = sinon.useFakeTimers();
this.logger.log({ message: 'foobar', serviceId: 15 });
logger.log({ message: 'foobar', serviceId: 15 });
clock.tick(61000);
clock.restore();
@ -65,9 +74,9 @@ describe('Logger', function () {
it('sends logs as gzipped ndjson', function () {
const timestamp = Date.now();
this.logger.log({ message: 'foobar', serviceId: 15 });
this.logger.log({ timestamp: 1337, message: 'foobar', serviceId: 15 });
this.logger.log({ message: 'foobar' }); // shold be ignored
logger.log({ message: 'foobar', serviceId: 15 });
logger.log({ timestamp: 1337, message: 'foobar', serviceId: 15 });
logger.log({ message: 'foobar' }); // shold be ignored
return Promise.delay(5500).then(() => {
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 () {
const timestamp = Date.now();
this.logger.logSystemMessage(
logger.logSystemMessage(
'Hello there!',
{ someProp: 'someVal' },
'Some event name',
);
return Promise.delay(5500).then(() => {
expect(this.eventTrackerStub).to.be.calledWith('Some event name', {
someProp: 'someVal',
});
const lines = this._req.body.split('\n');
expect(lines.length).to.equal(2);
expect(lines[1]).to.equal('');

View File

@ -1,7 +1,7 @@
import { Promise } from 'bluebird';
import { stripIndent } from 'common-tags';
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 { 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 { expect } from './lib/chai-config';
import * as logger from '../src/logger';
import prepare = require('./lib/prepare');
const extlinuxBackend = new ExtlinuxConfigBackend();
@ -28,12 +30,12 @@ describe('DeviceConfig', function () {
});
},
};
this.fakeLogger = {
logSystemMessage: spy(),
};
return (this.deviceConfig = new DeviceConfig({
logger: this.fakeLogger,
}));
this.logStub = stub(logger, 'logSystemMessage');
return (this.deviceConfig = new DeviceConfig());
});
after(function () {
this.logStub.restore();
});
// 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;
return promise.catch((_err) => {
expect(this.fakeLogger.logSystemMessage).to.be.calledOnce;
expect(this.fakeLogger.logSystemMessage).to.be.calledWith(
expect(this.logStub).to.be.calledOnce;
expect(this.logStub).to.be.calledWith(
'Attempt to change blacklisted config value initramfs',
{
error: 'Attempt to change blacklisted config value initramfs',
},
'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);
return promise.then(() => {
expect(this.fakeLogger.logSystemMessage).to.not.be.called;
return this.fakeLogger.logSystemMessage.resetHistory();
expect(this.logStub).to.not.be.called;
return this.logStub.resetHistory();
});
});
@ -168,8 +170,8 @@ describe('DeviceConfig', function () {
.setBootConfig(rpiConfigBackend, target)
.then(() => {
expect(child_process.exec).to.be.calledOnce;
expect(this.fakeLogger.logSystemMessage).to.be.calledTwice;
expect(this.fakeLogger.logSystemMessage.getCall(1).args[2]).to.equal(
expect(this.logStub).to.be.calledTwice;
expect(this.logStub.getCall(1).args[2]).to.equal(
'Apply boot config success',
);
expect(fsUtils.writeFileAtomic).to.be.calledWith(
@ -185,7 +187,7 @@ foobaz=bar\n\
);
(fsUtils.writeFileAtomic 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)
.then(() => {
expect(child_process.exec).to.be.calledOnce;
expect(this.fakeLogger.logSystemMessage).to.be.calledTwice;
expect(
this.fakeLogger.logSystemMessage.getCall(1).args[2],
).to.equal('Apply boot config success');
expect(this.logStub).to.be.calledTwice;
expect(this.logStub.getCall(1).args[2]).to.equal(
'Apply boot config success',
);
expect(fsUtils.writeFileAtomic).to.be.calledWith(
'./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();
(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');
});
});
this.upboardConfig = new DeviceConfig({
logger: this.fakeLogger,
});
this.upboardConfig = new DeviceConfig();
stub(child_process, 'exec').resolves();
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();
(fsUtils.writeFileAtomic as SinonStub).restore();
(config.get as SinonStub).restore();
this.fakeLogger.logSystemMessage.resetHistory();
this.logStub.resetHistory();
});
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',
};
this.fakeLogger.logSystemMessage.resetHistory();
this.logStub.resetHistory();
(child_process.exec as SinonSpy).resetHistory();
(fs.exists 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'],
}),
);
expect(this.fakeLogger.logSystemMessage).to.be.calledTwice;
return expect(
this.fakeLogger.logSystemMessage.getCall(1).args[2],
).to.equal('Apply boot config success');
expect(this.logStub).to.be.calledTwice;
return expect(this.logStub.getCall(1).args[2]).to.equal(
'Apply boot config success',
);
});
});
});

View File

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

View File

@ -2,29 +2,29 @@ import { expect } from 'chai';
import { stub, SinonStub } from 'sinon';
import { docker } from '../src/lib/docker-utils';
import * as logger from '../src/logger';
import Volume from '../src/compose/volume';
import logTypes = require('../src/lib/log-types');
const fakeLogger = {
logSystemMessage: stub(),
logSystemEvent: stub(),
};
const opts: any = { logger: fakeLogger };
describe('Compose volumes', () => {
let createVolumeStub: SinonStub;
let logSystemStub: SinonStub;
let logMessageStub: SinonStub;
before(() => {
createVolumeStub = stub(docker, 'createVolume');
logSystemStub = stub(logger, 'logSystemEvent');
logMessageStub = stub(logger, 'logSystemMessage');
});
after(() => {
createVolumeStub.restore();
logSystemStub.restore();
logMessageStub.restore();
});
describe('Parsing volumes', () => {
it('should correctly parse docker volumes', () => {
const volume = Volume.fromDockerVolume(opts, {
const volume = Volume.fromDockerVolume({
Driver: 'local',
Labels: {
'io.balena.supervised': 'true',
@ -54,19 +54,14 @@ describe('Compose volumes', () => {
});
it('should correctly parse compose volumes without an explicit driver', () => {
const volume = Volume.fromComposeObject(
'one_volume',
1032480,
{
const volume = Volume.fromComposeObject('one_volume', 1032480, {
driver_opts: {
opt1: 'test',
},
labels: {
'my-label': 'test-label',
},
},
opts,
);
});
expect(volume).to.have.property('appId').that.equals(1032480);
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', () => {
const volume = Volume.fromComposeObject(
'one_volume',
1032480,
{
const volume = Volume.fromComposeObject('one_volume', 1032480, {
driver: 'other',
driver_opts: {
opt1: 'test',
@ -101,9 +93,7 @@ describe('Compose volumes', () => {
labels: {
'my-label': 'test-label',
},
},
opts,
);
});
expect(volume).to.have.property('appId').that.equals(1032480);
expect(volume).to.have.property('name').that.equals('one_volume');
@ -130,23 +120,18 @@ describe('Compose volumes', () => {
describe('Generating docker options', () => {
afterEach(() => {
createVolumeStub.reset();
fakeLogger.logSystemEvent.reset();
fakeLogger.logSystemMessage.reset();
logSystemStub.reset();
logMessageStub.reset();
});
it('should correctly generate docker options', async () => {
const volume = Volume.fromComposeObject(
'one_volume',
1032480,
{
const volume = Volume.fromComposeObject('one_volume', 1032480, {
driver_opts: {
opt1: 'test',
},
labels: {
'my-label': 'test-label',
},
},
opts,
);
});
await volume.create();
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,
EngineSnapshotRecord,
} from '../src/local-mode';
import Logger from '../src/logger';
import ShortStackError from './lib/errors';
describe('LocalModeManager', () => {
@ -34,9 +33,8 @@ describe('LocalModeManager', () => {
await db.initialized;
dockerStub = sinon.stub(docker);
const loggerStub = (sinon.createStubInstance(Logger) as unknown) as Logger;
localMode = new LocalModeManager(loggerStub, supervisorContainerId);
localMode = new LocalModeManager(supervisorContainerId);
});
after(async () => {

View File

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