Skip to content

Commit 39fd9c8

Browse files
Added ability to provided the container listener strategy (logs/attach)
* added ability to get existing containers and not only new ones (to support the launch composition step where container already exists) * handle both cases of tty and non tty containers
1 parent 30d5d6e commit 39fd9c8

12 files changed

+1403
-504
lines changed

.eslintignore

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# /node_modules/* and /bower_components/* ignored by default
2+
3+
# Ignore built files except build/index.js
4+
build/*
5+
!build/index.js
6+
coverage/*
7+
8+
# Directory for instrumented libs generated by jscoverage/JSCover
9+
lib-cov
10+
11+
reports
12+
.DS_Store
13+
14+
# Until we move all tests to their own subdirectory, we ignore them in eslint
15+
*.spec.js

.eslintrc

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// Codefresh Code Style - eslint ruleset
2+
// Based on AirBnB.
3+
//
4+
// More details: https://codefresh-io.atlassian.net/wiki/display/COD/Code+Style+Guide
5+
{
6+
7+
"extends": "airbnb",
8+
"parserOptions": {
9+
"ecmaVersion": 6,
10+
"sourceType": "script",
11+
"ecmaFeatures": {
12+
"jsx": true
13+
}
14+
},
15+
16+
"rules": {
17+
"indent": [
18+
"error",
19+
4,
20+
{
21+
"SwitchCase": 1,
22+
"VariableDeclarator": 1
23+
}
24+
],
25+
"func-names": [
26+
"error",
27+
"never"
28+
],
29+
"quotes": [
30+
"error",
31+
"single",
32+
{
33+
"allowTemplateLiterals": true
34+
}
35+
],
36+
"no-underscore-dangle": "off",
37+
"no-param-reassign": "off",
38+
"no-else-return": "off",
39+
"arrow-body-style": "off",
40+
"strict": [
41+
"error",
42+
"global"
43+
],
44+
"no-multi-spaces": "off",
45+
"padded-blocks": "off",
46+
"import/no-extraneous-dependencies": [
47+
2,
48+
{
49+
"devDependencies": true
50+
}
51+
],
52+
"guard-for-in": "error",
53+
"no-console": "off",
54+
"comma-dangle": ["error", "only-multiline"],
55+
"quote-props": ["error", "consistent"]
56+
}
57+
}

.gitignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ coverage/*.html
55
.coverdata
66
.debug
77
coverage
8-
state.json
8+
state.json
9+
*.log

Dockerfile

+3-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
FROM node:onbuild
1+
FROM node:onbuild
2+
3+
CMD ["npm", "start", "-s"]

README.md

+9-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# cf-container-logger
2+
23
# required environment variables:
34
# FIREBASE_AUTH_URL - the main firebase ref to authenticate on initialization
45
# FIREBASE_SECRET - the secret key to write to the firebase auth url and all future derived urls
5-
# LOGGER_ID - logger id. if a container will include this id in its label, we will log it
6+
# LOGGER_ID - logger id. if a container will include this id in its label, we will log it
7+
# LOG_TO_CONSOLE - by default, logging to console is disabled and only logging to a file is enabled. set this env to log to console to
8+
9+
# Container labels
10+
# logging strategies
11+
# io.codefresh.loggerStrategy
12+
# logs - will get all container logs from the beginning
13+
# attach - will get all container logs from the point where attach was enabled. usefull for getting all interactive i/o

lib/ContainerLogger.js

+99
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
'use strict';
2+
3+
const Q = require('q');
4+
const logger = require('cf-logs').Logger('codefresh:containerLogger');
5+
const CFError = require('cf-errors');
6+
const LoggerStrategy = require('./enums').LoggerStrategy;
7+
8+
class ContainerLogger {
9+
10+
constructor(containerId, containerInterface, firebaseLogger, loggerStrategy) {
11+
this.containerId = containerId;
12+
this.containerInterface = containerInterface;
13+
this.firebaseLogger = firebaseLogger;
14+
this.loggerStrategy = loggerStrategy;
15+
this.tty = false;
16+
}
17+
18+
start() {
19+
return Q.ninvoke(this.containerInterface, 'inspect')
20+
.then((inspectedContainer) => {
21+
this.tty = inspectedContainer.Config.Tty;
22+
if (this.loggerStrategy === LoggerStrategy.ATTACH) {
23+
return this._getAttachStrategyStream();
24+
} else if (this.loggerStrategy === LoggerStrategy.LOGS) {
25+
return this._getLogsStrategyStream();
26+
} else {
27+
return Q.reject(new CFError(`Strategy: ${this.loggerStrategy} is not supported`));
28+
}
29+
})
30+
.then((stream) => {
31+
logger.info(`Attached stream to container: ${this.containerId}`);
32+
// Listening on the stream needs to be performed different depending if a tty is attached or not
33+
// See documentation of the docker api here: https://docs.docker.com/engine/reference/api/docker_remote_api_v1.24/#/attach-to-a-container
34+
if (this.tty) {
35+
this._handleTtyStream(stream);
36+
} else {
37+
this._handleNonTtyStream(stream);
38+
}
39+
40+
stream.on('end', () => {
41+
logger.info(`stream end event was fired for container: ${this.containerId}`);
42+
});
43+
}, (err) => {
44+
return Q.reject(new CFError({
45+
cause: err,
46+
message: `Failed to handle container:${this.containerId}`
47+
}));
48+
});
49+
}
50+
51+
_getAttachStrategyStream() {
52+
return Q.ninvoke(this.containerInterface, 'attach', {
53+
stream: true,
54+
stdout: true,
55+
stderr: true,
56+
tty: true
57+
});
58+
}
59+
60+
_getLogsStrategyStream() {
61+
return Q.ninvoke(this.containerInterface, 'logs', {
62+
follow: 1,
63+
stdout: 1,
64+
stderr: 1
65+
});
66+
}
67+
68+
_handleTtyStream(stream) {
69+
stream.on('data', (chunk) => {
70+
const buf = new Buffer(chunk);
71+
const message = buf.toString('utf8');
72+
this._logMessageToFirebase(message);
73+
});
74+
logger.info(`Listening on stream 'data' event for container: ${this.containerId}`);
75+
}
76+
77+
_handleNonTtyStream(stream) {
78+
stream.on('readable', () => {
79+
let header = stream.read(8);
80+
while (header !== null) {
81+
const payload = stream.read(header.readUInt32BE(4));
82+
if (payload === null) {
83+
break;
84+
}
85+
this._logMessageToFirebase(new Buffer(payload).toString('utf8'));
86+
header = stream.read(8);
87+
}
88+
});
89+
logger.info(`Listening on stream 'readable' event for container: ${this.containerId}`);
90+
}
91+
92+
_logMessageToFirebase(message) {
93+
this.firebaseLogger.child('logs').push(message);
94+
this.firebaseLogger.child('lastUpdate').set(new Date().getTime());
95+
}
96+
97+
}
98+
99+
module.exports = ContainerLogger;

lib/enums.js

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
'use strict';
2+
3+
const ContainerStatus = {
4+
CREATE: 'create'
5+
};
6+
const LoggerStrategy = {
7+
ATTACH: 'attach',
8+
LOGS: 'logs',
9+
ALL: ['attach', 'logs']
10+
};
11+
const ContainerHandlingStatus = {
12+
INITIALIZING: 'initializing',
13+
LISTENING: 'listening'
14+
};
15+
16+
17+
module.exports = {
18+
ContainerStatus,
19+
LoggerStrategy,
20+
ContainerHandlingStatus
21+
};
22+

lib/index.js

+16-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,18 @@
11
'use strict';
22

3-
var logger = require('./logger');
4-
logger(process.env.LOGGER_ID, process.env.FIREBASE_AUTH_URL, process.env.FIREBASE_SECRET);
3+
const path = require('path');
4+
const cflogs = require('cf-logs');
5+
6+
const loggerOptions = {
7+
filePath: path.join(__dirname, '../logs', 'logs.log'),
8+
console: process.env.LOG_TO_CONSOLE || false
9+
};
10+
cflogs.init(loggerOptions);
11+
12+
const Logger = require('./logger');
13+
14+
15+
const logger = new Logger(process.env.LOGGER_ID, process.env.FIREBASE_AUTH_URL, process.env.FIREBASE_SECRET, process.env.LISTEN_ON_EXISTING);
16+
17+
logger.validate();
18+
logger.start();

0 commit comments

Comments
 (0)