Skip to content

Commit baad313

Browse files
Support reporting to firebase metrics on logs and stop writing new logs when reaching the limit (#11)
1 parent 166cd94 commit baad313

File tree

7 files changed

+1572
-169
lines changed

7 files changed

+1572
-169
lines changed

lib/ContainerLogger.js

Lines changed: 54 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,35 @@
11
'use strict';
22

3+
const EventEmitter = require('events');
34
const Q = require('q');
45
const logger = require('cf-logs').Logger('codefresh:containerLogger');
56
const CFError = require('cf-errors');
67
const LoggerStrategy = require('./enums').LoggerStrategy;
78

8-
class ContainerLogger {
9+
class ContainerLogger extends EventEmitter {
910

10-
constructor(containerId, containerInterface, firebaseLogger, firebaseLastUpdate, loggerStrategy) {
11-
this.containerId = containerId;
12-
this.containerInterface = containerInterface;
13-
this.firebaseLogger = firebaseLogger;
14-
this.firebaseLastUpdate = firebaseLastUpdate;
15-
this.loggerStrategy = loggerStrategy;
16-
this.tty = false;
11+
constructor({
12+
containerId,
13+
containerInterface,
14+
firebaseLogger,
15+
firebaseLastUpdate,
16+
firebaseMetricsLogs,
17+
logSizeLimit,
18+
isWorkflowLogSizeExceeded, // eslint-disable-line
19+
loggerStrategy
20+
}) {
21+
super();
22+
this.containerId = containerId;
23+
this.containerInterface = containerInterface;
24+
this.firebaseLogger = firebaseLogger;
25+
this.firebaseLastUpdate = firebaseLastUpdate;
26+
this.firebaseMetricsLogs = firebaseMetricsLogs;
27+
this.loggerStrategy = loggerStrategy;
28+
this.tty = false;
29+
this.logSizeLimit = logSizeLimit;
30+
this.logSize = 0;
31+
this.isWorkflowLogSizeExceeded = isWorkflowLogSizeExceeded;
32+
this.stepFinished = false;
1733
}
1834

1935
start() {
@@ -34,18 +50,24 @@ class ContainerLogger {
3450
// See documentation of the docker api here: https://docs.docker.com/engine/reference/api/docker_remote_api_v1.24/#/attach-to-a-container
3551
if (this.tty) {
3652
this._handleTtyStream(stdout, false);
37-
this._handleTtyStream(stderr, true);
53+
if (stderr) {
54+
this._handleTtyStream(stderr, true);
55+
}
3856
} else {
3957
this._handleNonTtyStream(stdout, false);
4058
}
4159

4260
stdout.on('end', () => {
61+
this.stepFinished = true;
4362
logger.info(`stdout end event was fired for container: ${this.containerId}`);
4463
});
4564

46-
stderr.on('end', () => {
47-
logger.info(`stderr end event was fired for container: ${this.containerId}`);
48-
});
65+
if (stderr) {
66+
stderr.on('end', () => {
67+
this.stepFinished = true;
68+
logger.info(`stderr end event was fired for container: ${this.containerId}`);
69+
});
70+
}
4971
}, (err) => {
5072
return Q.reject(new CFError({
5173
cause: err,
@@ -105,13 +127,32 @@ class ContainerLogger {
105127
logger.info(`Listening on stream 'readable' event for container: ${this.containerId}`);
106128
}
107129

130+
_stepLogSizeExceeded() {
131+
return this.logSize > this.logSizeLimit;
132+
}
133+
108134
_logMessageToFirebase(message, isError) {
135+
if (this.logSizeLimit && (this._stepLogSizeExceeded() || this.isWorkflowLogSizeExceeded()) && !isError) {
136+
if (!this.logExceededLimitsNotified) {
137+
this.logExceededLimitsNotified = true;
138+
message = `\x1B[01;93mLog size exceeded for ${this._stepLogSizeExceeded() ? 'this step' : 'the workflow'}.\nThe step will continue to execute until it finished but new logs will not be stored.\x1B[0m\r\n`;
139+
} else {
140+
return;
141+
}
142+
}
143+
109144
if (isError) {
110145
message = `\x1B[31m${message}\x1B[0m`;
111146
}
112-
147+
113148
this.firebaseLogger.push(message);
114149
this.firebaseLastUpdate.set(new Date().getTime());
150+
151+
if (this.logSizeLimit) {
152+
this.logSize += Buffer.byteLength(message);
153+
this.firebaseMetricsLogs.child('total').set(this.logSize);
154+
}
155+
this.emit('message.logged');
115156
}
116157

117158
}

lib/index.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,14 @@ cflogs.init(loggerOptions);
1111

1212
const Logger = require('./logger');
1313

14-
15-
const logger = new Logger(process.env.LOGGER_ID, process.env.FIREBASE_AUTH_URL, process.env.FIREBASE_SECRET, process.env.LISTEN_ON_EXISTING);
14+
const logger = new Logger({
15+
loggerId: process.env.LOGGER_ID,
16+
firebaseAuthUrl: process.env.FIREBASE_AUTH_URL,
17+
firebaseSecret: process.env.FIREBASE_SECRET,
18+
findExistingContainers: process.env.LISTEN_ON_EXISTING,
19+
firebaseMetricsLogsUrl: process.env.FIREBASE_METRICS_LOGS_URL,
20+
logSizeLimit: process.env.LOG_SIZE_LIMIT ? (parseInt(process.env.LOG_SIZE_LIMIT) * 1000000) : undefined,
21+
});
1622

1723
logger.validate();
1824
logger.start();

lib/logger.js

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,23 @@ const ContainerLogger = require('./ContainerLogger');
1414

1515
class Logger {
1616

17-
constructor(loggerId, firebaseAuthUrl, firebaseSecret, findExistingContainers) {
17+
constructor({
18+
loggerId,
19+
firebaseAuthUrl,
20+
firebaseSecret,
21+
findExistingContainers,
22+
firebaseMetricsLogsUrl,
23+
logSizeLimit
24+
}) {
1825
this.state = { status: 'init' };
1926
this.firebaseAuthUrl = firebaseAuthUrl;
2027
this.firebaseSecret = firebaseSecret;
2128
this.loggerId = loggerId;
2229
this.findExistingContainers = findExistingContainers === 'true';
30+
this.firebaseMetricsLogsUrl = firebaseMetricsLogsUrl;
31+
this.logSizeLimit = logSizeLimit;
32+
this.containerLoggers = [];
33+
this.logSize = 0;
2334

2435
let dockerSockPath;
2536
if (fs.existsSync('/var/run/codefresh/docker.sock')) {
@@ -75,6 +86,7 @@ class Logger {
7586
return;
7687
}
7788
logger.info(`Authenticated to firebase url: ${this.firebaseAuthUrl}`);
89+
this.firebaseMetricsLogs = new Firebase(this.firebaseMetricsLogsUrl);
7890

7991
this._listenForNewContainers();
8092

@@ -118,6 +130,19 @@ class Logger {
118130
});
119131
}
120132

133+
134+
logLimitExceeded() {
135+
// TODO in the future when we allow a workflow to use multuple dinds, this will not be correct
136+
// we need to get the total size of logs from all dinds
137+
return this.logSizeLimit && this._getTotalLogSize() > this.logSizeLimit;
138+
}
139+
140+
_getTotalLogSize() {
141+
return _.reduce(this.containerLoggers, (sum, containerLogger) => {
142+
return sum + containerLogger.logSize;
143+
}, 0);
144+
}
145+
121146
/**
122147
* receives a container and decides if to start listening on it
123148
* @param loggerId
@@ -134,6 +159,12 @@ class Logger {
134159
const receivedFirebaseLastUpdateUrl = _.get(container,
135160
'Labels',
136161
_.get(container, 'Actor.Attributes'))['io.codefresh.logger.firebase.lastUpdateUrl'];
162+
const receivedFirebaseMetricsLogsUrl = _.get(container,
163+
'Labels',
164+
_.get(container, 'Actor.Attributes'))['io.codefresh.logger.firebase.metricsLogs'];
165+
const receivedLogSizeLimit = _.get(container,
166+
'Labels',
167+
_.get(container, 'Actor.Attributes'))['io.codefresh.logger.logSizeLimit'];
137168
const loggerStrategy = _.get(container, 'Labels', _.get(container, 'Actor.Attributes'))['io.codefresh.logger.strategy'];
138169

139170
if (!containerId) {
@@ -167,6 +198,11 @@ class Logger {
167198
return;
168199
}
169200

201+
if (!receivedFirebaseMetricsLogsUrl) {
202+
logger.error(`Container: ${containerId} does contain a loggerFirebaseMetricsLogsUrl label`);
203+
return;
204+
}
205+
170206
if (!loggerStrategy) {
171207
logger.error(`Container: ${containerId} does contain a loggerStrategy label`);
172208
return;
@@ -210,8 +246,33 @@ class Logger {
210246
return;
211247
}
212248

249+
let firebaseMetricsLogs;
250+
try {
251+
firebaseMetricsLogs = new Firebase(receivedFirebaseMetricsLogsUrl);
252+
} catch (err) {
253+
const error = new CFError({
254+
cause: err,
255+
message: `Failed to create a new firebase metricsLogs ref`
256+
});
257+
logger.error(error.toString());
258+
return;
259+
}
260+
261+
const logSizeLimit = receivedLogSizeLimit ? (parseInt(receivedLogSizeLimit) * 1000000) : undefined;
262+
213263
const containerInterface = this.docker.getContainer(containerId);
214-
const containerLogger = new ContainerLogger(containerId, containerInterface, firebaseLogger, firebaseLastUpdate, loggerStrategy);
264+
const containerLogger = new ContainerLogger({
265+
containerId,
266+
containerInterface,
267+
firebaseLogger,
268+
firebaseLastUpdate,
269+
firebaseMetricsLogs,
270+
logSizeLimit,
271+
isWorkflowLogSizeExceeded: this.logLimitExceeded.bind(this),
272+
loggerStrategy
273+
});
274+
this.containerLoggers.push(containerLogger);
275+
containerLogger.on('message.logged', this._updateTotalLogSize.bind(this));
215276

216277
containerLogger.start()
217278
.done(() => {
@@ -227,6 +288,11 @@ class Logger {
227288
});
228289
}
229290

291+
_updateTotalLogSize() {
292+
this.logSize = this._getTotalLogSize();
293+
this.firebaseMetricsLogs.child('total').set(this.logSize);
294+
}
295+
230296
/**
231297
* Will check if a container was already handled (no matter what the handling status is)
232298
* @param containerId

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
"dependencies": {
99
"cf-errors": "^0.1.11",
1010
"cf-logs": "git+https://github.com/codefresh-io/cf-logs.git#ceba4f309e52a077747a0c6bf9c3ad02e762dc4b",
11-
"dockerode": "^2.3.0",
1211
"docker-events": "0.0.2",
12+
"dockerode": "^2.3.0",
1313
"firebase": "git+https://github.com/codefresh-io/firebase.git#80b2ed883ff281cd67b53bd0f6a0bbd6f330fed5",
1414
"forever": "^0.15.3",
1515
"lodash": "^4.15.0",
@@ -22,6 +22,7 @@
2222
"gulp-env": "^0.2.0",
2323
"gulp-istanbul": "^0.10.4",
2424
"gulp-jshint": "^1.11.0",
25+
"gulp-mocha": "^6.0.0",
2526
"gulp-mocha-co": "^0.4.1-co.3",
2627
"gulp-rimraf": "^0.1.1",
2728
"isparta": "^4.0.0",

0 commit comments

Comments
 (0)