Skip to content

Commit 8f7e8cb

Browse files
authored
Improve debugging and logging (#88)
Improves debugging and logging. Subscribing to page events to catch errors from browser when rendering image. Possible to adjust log level using LOG_LEVEL environment variable when running as service. Possible to adjust console logging by configuration file when running as service. Closes #85
1 parent b448eea commit 8f7e8cb

File tree

13 files changed

+379
-28
lines changed

13 files changed

+379
-28
lines changed

default.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@
66
"enabled": false,
77
"collectDefaultMetrics": true,
88
"requestDurationBuckets": [1, 5, 7, 9, 11, 13, 15, 20, 30]
9+
},
10+
"logging": {
11+
"level": "info",
12+
"console": {
13+
"json": true,
14+
"colorize": false
15+
}
916
}
1017
},
1118
"rendering": {
@@ -17,6 +24,7 @@
1724
"clustering": {
1825
"mode": "browser",
1926
"maxConcurrency": 5
20-
}
27+
},
28+
"verboseLogging": false
2129
}
2230
}

dev.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@
66
"enabled": true,
77
"collectDefaultMetrics": true,
88
"requestDurationBuckets": [1, 5, 7, 9, 11, 13, 15, 20, 30]
9+
},
10+
"logging": {
11+
"level": "debug",
12+
"console": {
13+
"json": false,
14+
"colorize": true
15+
}
916
}
1017
},
1118
"rendering": {
@@ -17,6 +24,7 @@
1724
"clustering": {
1825
"mode": "browser",
1926
"maxConcurrency": 5
20-
}
27+
},
28+
"verboseLogging": false
2129
}
2230
}

docs/remote_rendering_using_docker.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,15 @@ You can enable [Prometheus](https://prometheus.io/) metrics endpoint `/metrics`
4949
export ENABLE_METRICS=true
5050
```
5151

52+
**Log level:**
53+
54+
Change the log level. Default is `info` and will include log messages with level `error`, `warning` and info.
55+
56+
57+
```bash
58+
export LOG_LEVEL=info
59+
```
60+
5261
## Configuration file
5362

5463
You can override certain settings by using a configuration file, see [default.json](https://github.com/grafana/grafana-image-renderer/tree/master/default.json) for defaults. Note that any configured environment variable takes precedence over configuration file settings.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@
3131
"prom-client": "^11.5.3",
3232
"puppeteer": "^2.0.0",
3333
"puppeteer-cluster": "^0.18.0",
34-
"unique-filename": "^1.1.0"
34+
"unique-filename": "^1.1.0",
35+
"winston": "^3.2.1"
3536
},
3637
"devDependencies": {
3738
"@types/express": "^4.11.1",

src/app.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,13 @@ async function main() {
3434
plugin.start();
3535
} else if (command === 'server') {
3636
let config: ServiceConfig = defaultServiceConfig;
37-
const logger = new ConsoleLogger();
3837

3938
if (argv.config) {
4039
try {
4140
const fileConfig = readJSONFileSync(argv.config);
4241
config = _.merge(config, fileConfig);
4342
} catch (e) {
44-
logger.error('failed to read config from path', argv.config, 'error', e);
43+
console.error('failed to read config from path', argv.config, 'error', e);
4544
return;
4645
}
4746
}
@@ -52,6 +51,7 @@ async function main() {
5251
timings = new MetricsBrowserTimings();
5352
}
5453

54+
const logger = new ConsoleLogger(config.service.logging);
5555
const browser = createBrowser(config.rendering, logger, timings);
5656
const server = new HttpServer(config, logger, browser);
5757

@@ -101,6 +101,10 @@ function populateServiceConfigFromEnv(config: ServiceConfig, env: NodeJS.Process
101101
config.service.port = parseInt(env['HTTP_PORT'] as string, 10);
102102
}
103103

104+
if (env['LOG_LEVEL']) {
105+
config.service.logging.level = env['LOG_LEVEL'] as string;
106+
}
107+
104108
if (env['IGNORE_HTTPS_ERRORS']) {
105109
config.rendering.ignoresHttpsErrors = env['IGNORE_HTTPS_ERRORS'] === 'true';
106110
}

src/browser/browser.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,9 +113,12 @@ export class Browser {
113113
await browser.newPage()
114114
);
115115

116+
this.addPageListeners(page);
117+
116118
return await this.takeScreenshot(page, options);
117119
} finally {
118120
if (page) {
121+
this.removePageListeners(page);
119122
await page.close();
120123
}
121124
if (browser) {
@@ -164,4 +167,69 @@ export class Browser {
164167

165168
return { filePath: options.filePath };
166169
}
170+
171+
addPageListeners(page: any) {
172+
page.on('error', this.logError.bind);
173+
page.on('pageerror', this.logPageError);
174+
page.on('requestfailed', this.logRequestFailed);
175+
page.on('console', this.logConsoleMessage);
176+
177+
if (this.config.verboseLogging) {
178+
page.on('request', this.logRequest);
179+
page.on('requestfinished', this.logRequestFinished);
180+
page.on('close', this.logPageClosed);
181+
}
182+
}
183+
184+
removePageListeners(page: any) {
185+
page.removeListener('error', this.logError);
186+
page.removeListener('pageerror', this.logPageError);
187+
page.removeListener('requestfailed', this.logRequestFailed);
188+
page.removeListener('console', this.logConsoleMessage);
189+
190+
if (this.config.verboseLogging) {
191+
page.removeListener('request', this.logRequest);
192+
page.removeListener('requestfinished', this.logRequestFinished);
193+
page.removeListener('close', this.logPageClosed);
194+
}
195+
}
196+
197+
logError = (err: Error) => {
198+
this.log.error('Browser page crashed', 'error', err.toString());
199+
};
200+
201+
logPageError = (err: Error) => {
202+
this.log.error('Browser uncaught exception', 'error', err.toString());
203+
};
204+
205+
logConsoleMessage = (msg: any) => {
206+
const msgType = msg.type();
207+
if (!this.config.verboseLogging && msgType !== 'error') {
208+
return;
209+
}
210+
211+
const loc = msg.location();
212+
if (msgType === 'error') {
213+
this.log.error('Browser console error', 'msg', msg.text(), 'url', loc.url, 'line', loc.lineNumber, 'column', loc.columnNumber);
214+
return;
215+
}
216+
217+
this.log.debug(`Browser console ${msgType}`, 'msg', msg.text(), 'url', loc.url, 'line', loc.lineNumber, 'column', loc.columnNumber);
218+
};
219+
220+
logRequest = (req: any) => {
221+
this.log.debug('Browser request', 'url', req._url, 'method', req._url);
222+
};
223+
224+
logRequestFailed = (req: any) => {
225+
this.log.error('Browser request failed', 'url', req._url, 'method', req._method);
226+
};
227+
228+
logRequestFinished = (req: any) => {
229+
this.log.debug('Browser request finished', 'url', req._url, 'method', req._method);
230+
};
231+
232+
logPageClosed = () => {
233+
this.log.debug('Browser page closed');
234+
};
167235
}

src/browser/clustered.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,12 @@ export class ClusteredBrowser extends Browser {
3131
// set timezone
3232
await page.emulateTimezone(data.timezone);
3333
}
34-
return await this.takeScreenshot(page, data);
34+
35+
try {
36+
return await this.takeScreenshot(page, data);
37+
} finally {
38+
this.removePageListeners(page);
39+
}
3540
});
3641
}
3742

src/browser/reusable.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,12 @@ export class ReusableBrowser extends Browser {
3939
await page.emulateTimezone(options.timezone);
4040
}
4141

42+
this.addPageListeners(page);
43+
4244
return await this.takeScreenshot(page, options);
4345
} finally {
4446
if (page) {
47+
this.removePageListeners(page);
4548
await page.close();
4649
}
4750
if (context) {

src/config.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export interface RenderingConfig {
1212
timingMetrics: boolean;
1313
mode: string;
1414
clustering: ClusteringConfig;
15+
verboseLogging: boolean;
1516
}
1617

1718
export interface MetricsConfig {
@@ -20,11 +21,23 @@ export interface MetricsConfig {
2021
requestDurationBuckets: number[];
2122
}
2223

24+
export interface ConsoleLoggerConfig {
25+
level?: string;
26+
json: boolean;
27+
colorize: boolean;
28+
}
29+
30+
export interface LoggingConfig {
31+
level: string;
32+
console?: ConsoleLoggerConfig;
33+
}
34+
2335
export interface ServiceConfig {
2436
service: {
2537
host?: string;
2638
port: number;
2739
metrics: MetricsConfig;
40+
logging: LoggingConfig;
2841
};
2942
rendering: RenderingConfig;
3043
}
@@ -49,6 +62,7 @@ const defaultRenderingConfig: RenderingConfig = {
4962
mode: 'browser',
5063
maxConcurrency: 5,
5164
},
65+
verboseLogging: false,
5266
};
5367

5468
export const defaultServiceConfig: ServiceConfig = {
@@ -60,6 +74,13 @@ export const defaultServiceConfig: ServiceConfig = {
6074
collectDefaultMetrics: true,
6175
requestDurationBuckets: [0.5, 1, 3, 5, 7, 10, 20, 30, 60],
6276
},
77+
logging: {
78+
level: 'info',
79+
console: {
80+
json: true,
81+
colorize: false,
82+
},
83+
},
6384
},
6485
rendering: defaultRenderingConfig,
6586
};

src/logger.ts

Lines changed: 70 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,89 @@
1+
import winston = require('winston');
2+
import { LoggingConfig } from './config';
3+
4+
export interface LogWriter {
5+
write(message, encoding);
6+
}
7+
18
export interface Logger {
9+
writer: LogWriter;
210
debug(message?: string, ...optionalParams: any[]);
311
info(message?: string, ...optionalParams: any[]);
412
warn(message?: string, ...optionalParams: any[]);
513
error(message?: string, ...optionalParams: any[]);
614
}
715

8-
export class ConsoleLogger {
9-
debug(message?: string, ...optionalParams: any[]) {
10-
console.debug(message, ...optionalParams);
16+
export class ConsoleLogger implements Logger {
17+
writer: LogWriter;
18+
logger: winston.Logger;
19+
20+
constructor(config: LoggingConfig) {
21+
const options2 = {
22+
console: {
23+
level: 'debug',
24+
handleExceptions: true,
25+
colorize: true,
26+
},
27+
};
28+
29+
const transports: any[] = [];
30+
31+
if (config.console) {
32+
const options: any = {
33+
exitOnError: false,
34+
};
35+
if (config.console.level) {
36+
options.level = config.console.level;
37+
}
38+
const formatters: any[] = [];
39+
if (config.console.colorize) {
40+
formatters.push(winston.format.colorize());
41+
}
42+
43+
if (config.console.json) {
44+
formatters.push(winston.format.json());
45+
} else {
46+
formatters.push(winston.format.align());
47+
formatters.push(winston.format.simple());
48+
}
49+
50+
options.format = winston.format.combine(...(formatters as any));
51+
transports.push(new winston.transports.Console(options));
52+
}
53+
54+
this.logger = winston.createLogger({
55+
level: config.level,
56+
exitOnError: false,
57+
transports: transports,
58+
});
59+
60+
this.writer = {
61+
write: message => {
62+
this.logger.info(message);
63+
},
64+
};
1165
}
1266

13-
info(message?: string, ...optionalParams: any[]) {
14-
console.info(message, ...optionalParams);
67+
debug(message: string, ...optionalParams: any[]) {
68+
this.logger.debug(message, optionalParams);
1569
}
1670

17-
warn(message?: string, ...optionalParams: any[]) {
18-
console.warn(message, ...optionalParams);
71+
info(message: string, ...optionalParams: any[]) {
72+
this.logger.info(message, optionalParams);
1973
}
2074

21-
error(message?: string, ...optionalParams: any[]) {
22-
console.error(message, ...optionalParams);
75+
warn(message: string, ...optionalParams: any[]) {
76+
this.logger.warn(message, optionalParams);
77+
}
78+
79+
error(message: string, ...optionalParams: any[]) {
80+
this.logger.error(message, optionalParams);
2381
}
2482
}
2583

26-
export class PluginLogger {
84+
export class PluginLogger implements Logger {
85+
writer: LogWriter;
86+
2787
private logEntry(level: string, message?: string, ...optionalParams: any[]) {
2888
const logEntry = {
2989
'@level': level,

0 commit comments

Comments
 (0)