I don’t have much experience with nodejs/js, so I want to ask for help solving a problem with the nodejs, express framework and express logging middleware morgan.
I import the logging from the middleware rather than placing it directly in the express app.js 'cause I need to get a custom log format etc. from middleware. My main express app.js:
import express from 'express';
import createError from 'http-errors';
import cookieParser from 'cookie-parser';
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// Express Router
import { router as indexRouter } from './routes/index.js';
import { router as healthCheck } from './routes/healthz.js';
import { error, access } from './lib/logger.mjs';
// Main Express App
const app = express();
/*
Log file settings
*/
app.set('logging', true);
app.set('logFileName', fs.createWriteStream(path.join(__dirname, 'access.log'), { flags: 'a' }));
/*
Middlewares
*/
app.use(error); // Log only 4xx and 5xx responses to console
app.use(access); // Log all requests to access.log
app.use(cookieParser());
app.use(express.json());
app.use(
express.urlencoded({
extended: true
})
);
// View engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');
app.use(express.static(path.join(__dirname, 'public')));
app.use('/', indexRouter);
app.use('/healthz', healthCheck);
/*
...
Part of the code has been excluded because it is not relevant to the question
...
*/
// module.exports = app;
export { app };
My logger middleware:
/*
Logging
*/
"use strict";
import chalk from 'chalk'; // https://www.npmjs.com/package/chalk
import morgan from 'morgan';
const logger = morgan;
import { createStream } from 'rotating-file-stream';
const rfc = createStream;
import path from 'node:path';
import fs from 'node:fs';
import { fileURLToPath } from 'node:url';
const logStreamName = (req, res) => {
return req.app.get('logFileName');
};
// logStreamName.write('--- START LOG ---');
// 'morgan' original code part
function headersSent (res) {
/*
// istanbul ignore next: node.js 0.8 support
res.headersSent > true
res._header > HTTP/1.1 400 Bad Request
res.header > [Function: header]
*/
return typeof res.headersSent !== 'boolean'
? Boolean(res._header)
: res.headersSent
}
// Colored status token
logger.token('marked-status', (req, res) => {
// let status = res.statusCode;
let status = headersSent(res) ? res.statusCode : undefined
function markedStatus (status) {
// .toString()
return status >= 500 ? chalk.bold.yellow(status.toString())
: status >= 400 ? chalk.bold.red(status.toString())
: status >= 300 ? chalk.bold.cyan(status.toString())
: status >= 200 ? chalk.bold.green(status.toString())
: chalk.reset(status)
};
return markedStatus(status);
});
// Custom format
logger.format('custom', [
':remote-addr', '-',
':remote-user',
'[:date]',
':method', ':url', 'HTTP/:http-version',
':marked-status',
':res[content-length]',
':referrer',
':user-agent'].join(' ')
);
/*
Log only 4xx and 5xx responses to console (process.stdout) and `error.log`.
Standard Apache common log output.
Format: ':remote-addr - :remote-user [:date[clf]] ":method :url HTTP/:http-version"
:status :res[content-length]'
*/
const error = logger('custom', {
skip: function (req, res) {
return res.statusCode < 400;
}
});
/*
Log all requests to `access.log`. Standard Apache 'combined' log output.
Format: ':remote-addr - :remote-user [:date[clf]] ":method :url HTTP/:http-version"
:status :res[content-length] ":referrer" ":user-agent"'
*/
const access = logger('combined', {
// stream: (req, res) => { return req.app.get('logFileName')},
stream: logStreamName
/*
skip: function (req, res) {
console.log(req.app.get('logFileName'));
return false
}
*/
});
export { error, access };
One of the goals I want is to create a log file, configure its rotation, and more in the main app.js. I pass the parameters of the write stream to the middleware level using the setting made through the method app.set:
app.set('logging', true);
app.set('logFileName', fs.createWriteStream(path.join(__dirname, 'access.log'), { flags: 'a' }));
According to the express.js documentation I can access these values at the middleware level via req.app.get('setting'). I implemented this in a function the result of which is assigned to a variable logStreamName at the middleware level.
But when executing the code and the first logging event occurs, I get the error:
/project/node_modules/morgan/index.js:130
stream.write(line + '\n')
^
TypeError: stream.write is not a function
at Array.logRequest (/project/node_modules/morgan/index.js:130:14)
at listener (/project/node_modules/morgan/node_modules/on-finished/index.js:169:15)
at onFinish (/project/node_modules/morgan/node_modules/on-finished/index.js:100:5)
at callback (/project/node_modules/morgan/node_modules/ee-first/index.js:55:10)
at ServerResponse.onevent (/project/node_modules/morgan/node_modules/ee-first/index.js:93:5)
at ServerResponse.emit (node:events:530:35)
at onFinish (node:_http_outgoing:1005:10)
at afterWrite (node:internal/streams/writable:701:5)
at afterWriteTick (node:internal/streams/writable:687:10)
at process.processTicksAndRejections (node:internal/process/task_queues:81:21)
Node.js v20.11.1
I previously tested the operation of the function and the result returned by part of the test code in skip: option with output to the console (now commented out). The function returns the correct WriteStream value (I removed part of the output):
WriteStream {
fd: 15,
path: '/project/access.log',
flags: 'a',
mode: 438,
flush: false,
start: undefined,
pos: undefined,
bytesWritten: 0,
_events: {
close: undefined,
error: undefined,
prefinish: undefined,
finish: undefined,
drain: undefined,
[Symbol(kConstruct)]: undefined
},
_writableState: WritableState {
highWaterMark: 16384,
length: 0,
corked: 0,
onwrite: [Function: bound onwrite],
writelen: 0,
bufferedIndex: 0,
pendingcb: 0,
[Symbol(kState)]: 17580812,
[Symbol(kBufferedValue)]: null
},
_maxListeners: undefined,
_eventsCount: 0,
[Symbol(kFs)]: {
},
promises: [Getter]
},
[Symbol(kIsPerformingIO)]: false,
[Symbol(shapeMode)]: true,
[Symbol(kCapture)]: false
}
I ask you to help me understand why my function does not work in the stream: option, although it works without problems in the skip: option and in other parts of the middleware code? As I understand it, when used in the stream: option, the function does not return the correct value, although according to the Express.js documentation I have access to the variables req & res on the middleware level. Please help and thank you in advance!