Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/transports.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ looking for daily log rotation see [DailyRotateFile](#dailyrotatefile-transport)
* __silent:__ Boolean flag indicating whether to suppress output (default false).
* __eol:__ Line-ending character to use. (default: `os.EOL`).
* __lazy:__ If true, log files will be created on demand, not at the initialization time.
* __allowSymlinks:__ Boolean flag indicating whether to allow symlinks (default true).
* __filename:__ The filename of the logfile to write output to.
* __maxsize:__ Max size in bytes of the logfile, if the size is exceeded then a new file is created, a counter will become a suffix of the log file.
* __maxFiles:__ Limit the number of files created when the size of the logfile is exceeded.
Expand Down
17 changes: 16 additions & 1 deletion lib/winston/transports/file.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
this.eol = (typeof options.eol === 'string') ? options.eol : os.EOL;
this.tailable = options.tailable || false;
this.lazy = options.lazy || false;
this.allowSymlinks = (typeof options.allowSymlinks === 'boolean') ? options.allowSymlinks : true;

// Internal state variables representing the number of files this instance
// has created and the current size (in bytes) of the current logfile.
Expand Down Expand Up @@ -329,7 +330,7 @@
if (callback) callback(null, results);
});

function add(buff, attempt) {

Check warning on line 333 in lib/winston/transports/file.js

View workflow job for this annotation

GitHub Actions / Tests (20)

'buff' is already declared in the upper scope on line 287 column 9

Check warning on line 333 in lib/winston/transports/file.js

View workflow job for this annotation

GitHub Actions / Tests (18)

'buff' is already declared in the upper scope on line 287 column 9

Check warning on line 333 in lib/winston/transports/file.js

View workflow job for this annotation

GitHub Actions / Tests (16)

'buff' is already declared in the upper scope on line 287 column 9
try {
const log = JSON.parse(buff);
if (check(log)) {
Expand Down Expand Up @@ -390,7 +391,7 @@
return true;
}

function normalizeQuery(options) {

Check warning on line 394 in lib/winston/transports/file.js

View workflow job for this annotation

GitHub Actions / Tests (20)

'options' is already declared in the upper scope on line 279 column 9

Check warning on line 394 in lib/winston/transports/file.js

View workflow job for this annotation

GitHub Actions / Tests (18)

'options' is already declared in the upper scope on line 279 column 9

Check warning on line 394 in lib/winston/transports/file.js

View workflow job for this annotation

GitHub Actions / Tests (16)

'options' is already declared in the upper scope on line 279 column 9
options = options || {};

// limit
Expand Down Expand Up @@ -610,7 +611,21 @@
const fullpath = path.join(this.dirname, this.filename);

debug('create stream start', fullpath, this.options);
const dest = fs.createWriteStream(fullpath, this.options)

const streamOptions = Object.assign({}, this.options);
if (!this.allowSymlinks) {
/* eslint-disable no-bitwise */
if (streamOptions.flags === 'a') {
streamOptions.flags = fs.constants.O_APPEND | fs.constants.O_CREAT | fs.constants.O_WRONLY | fs.constants.O_NOFOLLOW;
} else if (streamOptions.flags === 'w') {
streamOptions.flags = fs.constants.O_CREAT | fs.constants.O_TRUNC | fs.constants.O_WRONLY | fs.constants.O_NOFOLLOW;
} else if (typeof streamOptions.flags === 'number') {
streamOptions.flags |= fs.constants.O_NOFOLLOW;
}
/* eslint-enable no-bitwise */
}

const dest = fs.createWriteStream(fullpath, streamOptions)
// TODO: What should we do with errors here?
.on('error', err => debug(err))
.on('close', () => debug('close', dest.path, dest.bytesWritten))
Expand Down
1 change: 1 addition & 0 deletions lib/winston/transports/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ declare namespace winston {
eol?: string;
tailable?: boolean;
lazy?: boolean;
allowSymlinks?: boolean;
}

interface FileTransportInstance extends Transport {
Expand Down
73 changes: 73 additions & 0 deletions test/unit/winston/transports/file.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,79 @@ describe('File Transport', function () {
});
});

describe('Symlink Option', function () {
it('should not write to a symlink when allowSymlinks is false', async function () {
const targetFilename = 'target.log';
const symlinkFilename = 'symlink.log';
const targetPath = getFilePath(targetFilename);
const symlinkPath = getFilePath(symlinkFilename);

// Create target file
fs.writeFileSync(targetPath, 'initial content');

// Create symlink
try {
fs.symlinkSync(targetPath, symlinkPath);
} catch (err) {
if (err.code !== 'EEXIST') throw err;
}

const transport = new winston.transports.File({
dirname: testLogFixturesPath,
filename: symlinkFilename,
allowSymlinks: false
});

// We expect this to fail silently or emit an error, but definitely NOT write to target
// Using a small amount of data to avoid filling the buffer and hanging
await logToTransport(transport, { kbytes: 1 });

// Give it a moment to potentially flush (or fail)
await new Promise(resolve => setTimeout(resolve, 100));

const content = fs.readFileSync(targetPath, 'utf8');
assert.strictEqual(
content,
'initial content',
'Target file should not be modified'
);
});

it('should write to a symlink when allowSymlinks is true (default)', async function () {
const targetFilename = 'target_default.log';
const symlinkFilename = 'symlink_default.log';
const targetPath = getFilePath(targetFilename);
const symlinkPath = getFilePath(symlinkFilename);

// Create target file
fs.writeFileSync(targetPath, 'initial content');

// Create symlink
try {
fs.symlinkSync(targetPath, symlinkPath);
} catch (err) {
if (err.code !== 'EEXIST') throw err;
}

const transport = new winston.transports.File({
dirname: testLogFixturesPath,
filename: symlinkFilename
// allowSymlinks: true is default
});

await logToTransport(transport, { kbytes: 1 });

// Wait for write
await new Promise(resolve => setTimeout(resolve, 500));

const content = fs.readFileSync(targetPath, 'utf8');
assert.notStrictEqual(
content,
'initial content',
'Target file should be modified'
);
});
});

// TODO: Reintroduce these tests
//
Expand Down
Loading