file-timestamp-stream
Advanced tools
Comparing version 2.0.0 to 2.1.0
# Changelog | ||
## v2.1.0 2018-09-17 | ||
* Close file if its file name is already changed even if there is no new data | ||
written: this check is made by interval timer. | ||
## v2.0.0 2018-09-14 | ||
@@ -4,0 +9,0 @@ |
@@ -5,11 +5,17 @@ /// <reference types="node" /> | ||
export interface FileTimestampStreamOptions extends WritableOptions { | ||
/** a string with [flags](https://nodejs.org/api/fs.html#fs_fs_open_path_flags_mode_callback) for opened stream (default: `'a'`) */ | ||
flags?: string | null; | ||
/** a custom [fs](https://nodejs.org/api/fs.html) module (optional) */ | ||
fs?: typeof fs; | ||
/** a template for new filenames (default: `'out.log'`) */ | ||
path?: string; | ||
} | ||
export declare class FileTimestampStream extends Writable { | ||
static readonly CLOSE_UNUSED_FILE_AFTER: number; | ||
readonly flags: string; | ||
readonly fs: typeof fs; | ||
readonly path: string; | ||
/** contains last opened filename */ | ||
protected currentFilename?: string; | ||
/** contains current [fs.WriteStream](https://nodejs.org/api/fs.html#fs_class_fs_writestream) object */ | ||
protected stream?: WriteStream; | ||
@@ -20,2 +26,4 @@ private destroyed; | ||
private streamErrorHandlers; | ||
private closer?; | ||
private closers; | ||
constructor(options?: FileTimestampStreamOptions); | ||
@@ -29,3 +37,8 @@ _write(chunk: any, encoding: string, callback: (error?: Error | null) => void): void; | ||
_destroy(error: Error | null, callback: (error: Error | null) => void): void; | ||
/** Override this */ | ||
/** | ||
* This method can be overriden in subclass | ||
* | ||
* The method generates a filename for new files. By default it returns new | ||
* filename based on path and current time. | ||
*/ | ||
protected newFilename(): string; | ||
@@ -32,0 +45,0 @@ private rotate; |
@@ -7,7 +7,6 @@ "use strict"; | ||
const stream_1 = require("stream"); | ||
const timers_obj_1 = require("timers-obj"); | ||
const ultra_strftime_1 = tslib_1.__importDefault(require("ultra-strftime")); | ||
// tslint:disable-next-line:no-var-requires | ||
const finished = require('stream.finished'); // TODO: wait for new typings for node | ||
// tslint:disable-next-line:strict-type-predicates | ||
const HAS_DESTROY = typeof stream_1.Writable.prototype.destroy === 'function'; | ||
class FileTimestampStream extends stream_1.Writable { | ||
@@ -20,2 +19,3 @@ constructor(options = {}) { | ||
this.streamErrorHandlers = new Map(); | ||
this.closers = new Map(); | ||
this.flags = options.flags || 'a'; | ||
@@ -69,3 +69,3 @@ this.fs = options.fs || fs_1.default; | ||
if (this.streamErrorHandlers.size > 0) { | ||
this.streamErrorHandlers.forEach((handler, filename) => { | ||
for (const [filename, handler] of this.streamErrorHandlers) { | ||
const stream = this.streams.get(filename); | ||
@@ -75,27 +75,38 @@ if (stream) { | ||
} | ||
}); | ||
} | ||
this.streamErrorHandlers.clear(); | ||
} | ||
if (HAS_DESTROY) { | ||
if (this.streamCancelFinishers.size > 0) { | ||
this.streamCancelFinishers.forEach((cancel, filename) => { | ||
cancel(); | ||
this.streamCancelFinishers.delete(filename); | ||
}); | ||
this.streamCancelFinishers.clear(); | ||
if (this.streamCancelFinishers.size > 0) { | ||
for (const [filename, cancel] of this.streamCancelFinishers) { | ||
cancel(); | ||
this.streamCancelFinishers.delete(filename); | ||
} | ||
this.streamCancelFinishers.clear(); | ||
} | ||
if (this.streams.size > 0) { | ||
if (HAS_DESTROY) { | ||
this.streams.forEach((stream) => { | ||
for (const stream of this.streams.values()) { | ||
// tslint:disable-next-line:strict-type-predicates | ||
if (typeof stream.destroy === 'function') { | ||
stream.destroy(); | ||
}); | ||
} | ||
} | ||
this.streams.clear(); | ||
} | ||
if (this.closers.size > 0) { | ||
for (const timer of this.closers.values()) { | ||
timer.remove(); | ||
} | ||
this.streams.clear(); | ||
} | ||
this.destroyed = true; | ||
this.stream = undefined; | ||
this.closer = undefined; | ||
callback(error); | ||
} | ||
/** Override this */ | ||
/** | ||
* This method can be overriden in subclass | ||
* | ||
* The method generates a filename for new files. By default it returns new | ||
* filename based on path and current time. | ||
*/ | ||
newFilename() { | ||
@@ -106,13 +117,12 @@ return ultra_strftime_1.default(this.path, new Date()); | ||
const newFilename = this.newFilename(); | ||
if (newFilename !== this.currentFilename) { | ||
if (this.currentFilename && this.stream) { | ||
this.stream.close(); | ||
const streamErrorHandler = this.streamErrorHandlers.get(this.currentFilename); | ||
const { currentFilename, stream, closer } = this; | ||
if (newFilename !== currentFilename) { | ||
if (currentFilename && stream && closer) { | ||
closer.remove(); | ||
stream.end(); | ||
const streamErrorHandler = this.streamErrorHandlers.get(currentFilename); | ||
if (streamErrorHandler) { | ||
this.stream.removeListener('error', streamErrorHandler); | ||
this.streamErrorHandlers.delete(this.currentFilename); | ||
stream.removeListener('error', streamErrorHandler); | ||
this.streamErrorHandlers.delete(currentFilename); | ||
} | ||
if (!HAS_DESTROY) { | ||
this.streams.delete(this.currentFilename); | ||
} | ||
} | ||
@@ -129,10 +139,22 @@ const newStream = this.fs.createWriteStream(newFilename, { | ||
this.streamErrorHandlers.set(newFilename, newStreamErrorHandler); | ||
if (HAS_DESTROY) { | ||
const newStreamCancelFinisher = finished(newStream, () => { | ||
const newTimer = timers_obj_1.interval(FileTimestampStream.CLOSE_UNUSED_FILE_AFTER, () => { | ||
if (newFilename !== this.newFilename()) { | ||
newTimer.remove(); | ||
this.closers.delete(newFilename); | ||
newStream.end(); | ||
} | ||
}); | ||
this.closer = closer; | ||
this.closers.set(newFilename, newTimer); | ||
const newStreamCancelFinisher = finished(newStream, () => { | ||
newTimer.remove(); | ||
this.closers.delete(newFilename); | ||
// tslint:disable-next-line:strict-type-predicates | ||
if (typeof newStream.destroy === 'function') { | ||
newStream.destroy(); | ||
this.streamCancelFinishers.delete(newFilename); | ||
this.streams.delete(newFilename); | ||
}); | ||
this.streamCancelFinishers.set(newFilename, newStreamCancelFinisher); | ||
} | ||
} | ||
this.streamCancelFinishers.delete(newFilename); | ||
this.streams.delete(newFilename); | ||
}); | ||
this.streamCancelFinishers.set(newFilename, newStreamCancelFinisher); | ||
this.currentFilename = newFilename; | ||
@@ -142,3 +164,4 @@ } | ||
} | ||
FileTimestampStream.CLOSE_UNUSED_FILE_AFTER = 1000; | ||
exports.FileTimestampStream = FileTimestampStream; | ||
exports.default = FileTimestampStream; |
{ | ||
"name": "file-timestamp-stream", | ||
"version": "2.0.0", | ||
"version": "2.1.0", | ||
"description": "Writing stream with file rotating based on timestamp", | ||
@@ -27,2 +27,3 @@ "main": "lib/file-timestamp-stream.js", | ||
"stream.finished": "^1.0.1", | ||
"timers-obj": "^0.2.1", | ||
"tslib": "^1.9.3", | ||
@@ -29,0 +30,0 @@ "ultra-strftime": "^1.0.2" |
@@ -9,3 +9,4 @@ # file-timestamp-stream | ||
[stream.Writable](https://nodejs.org/api/stream.html#stream_class_stream_writable) | ||
to a file which is automatically rotated based on current time. | ||
to a file which is automatically rotated based on current time and uses | ||
[strftime](https://www.npmjs.com/package/strftime) template for file names. | ||
@@ -115,3 +116,3 @@ ## Requirements | ||
* `currentFilename` contains last opened filename | ||
* `stream` contains | ||
* `stream` contains current | ||
[fs.WriteStream](https://nodejs.org/api/fs.html#fs_class_fs_writestream) | ||
@@ -118,0 +119,0 @@ object |
@@ -5,2 +5,3 @@ /// <reference types="node" /> | ||
import { Writable, WritableOptions } from 'stream' | ||
import { interval, Interval } from 'timers-obj' | ||
import strftime from 'ultra-strftime' | ||
@@ -11,8 +12,8 @@ | ||
// tslint:disable-next-line:strict-type-predicates | ||
const HAS_DESTROY = typeof Writable.prototype.destroy === 'function' | ||
export interface FileTimestampStreamOptions extends WritableOptions { | ||
/** a string with [flags](https://nodejs.org/api/fs.html#fs_fs_open_path_flags_mode_callback) for opened stream (default: `'a'`) */ | ||
flags?: string | null | ||
/** a custom [fs](https://nodejs.org/api/fs.html) module (optional) */ | ||
fs?: typeof fs | ||
/** a template for new filenames (default: `'out.log'`) */ | ||
path?: string | ||
@@ -22,2 +23,4 @@ } | ||
export class FileTimestampStream extends Writable { | ||
static readonly CLOSE_UNUSED_FILE_AFTER = 1000 | ||
readonly flags: string | ||
@@ -27,3 +30,5 @@ readonly fs: typeof fs | ||
/** contains last opened filename */ | ||
protected currentFilename?: string | ||
/** contains current [fs.WriteStream](https://nodejs.org/api/fs.html#fs_class_fs_writestream) object */ | ||
protected stream?: WriteStream | ||
@@ -35,2 +40,4 @@ | ||
private streamErrorHandlers: Map<string, (err: Error) => void> = new Map() | ||
private closer?: Interval | ||
private closers: Map<string, Interval> = new Map() | ||
@@ -91,3 +98,3 @@ constructor (options: FileTimestampStreamOptions = {}) { | ||
if (this.streamErrorHandlers.size > 0) { | ||
this.streamErrorHandlers.forEach((handler, filename) => { | ||
for (const [filename, handler] of this.streamErrorHandlers) { | ||
const stream = this.streams.get(filename) | ||
@@ -97,25 +104,31 @@ if (stream) { | ||
} | ||
}) | ||
} | ||
this.streamErrorHandlers.clear() | ||
} | ||
if (HAS_DESTROY) { | ||
if (this.streamCancelFinishers.size > 0) { | ||
this.streamCancelFinishers.forEach((cancel, filename) => { | ||
cancel() | ||
this.streamCancelFinishers.delete(filename) | ||
}) | ||
this.streamCancelFinishers.clear() | ||
if (this.streamCancelFinishers.size > 0) { | ||
for (const [filename, cancel] of this.streamCancelFinishers) { | ||
cancel() | ||
this.streamCancelFinishers.delete(filename) | ||
} | ||
this.streamCancelFinishers.clear() | ||
} | ||
if (this.streams.size > 0) { | ||
if (HAS_DESTROY) { | ||
this.streams.forEach((stream) => { | ||
for (const stream of this.streams.values()) { | ||
// tslint:disable-next-line:strict-type-predicates | ||
if (typeof stream.destroy === 'function') { | ||
stream.destroy() | ||
}) | ||
} | ||
} | ||
this.streams.clear() | ||
} | ||
if (this.closers.size > 0) { | ||
for (const timer of this.closers.values()) { | ||
timer.remove() | ||
} | ||
this.streams.clear() | ||
} | ||
this.destroyed = true | ||
this.stream = undefined | ||
this.closer = undefined | ||
@@ -125,3 +138,8 @@ callback(error) | ||
/** Override this */ | ||
/** | ||
* This method can be overriden in subclass | ||
* | ||
* The method generates a filename for new files. By default it returns new | ||
* filename based on path and current time. | ||
*/ | ||
protected newFilename (): string { | ||
@@ -133,14 +151,15 @@ return strftime(this.path, new Date()) | ||
const newFilename = this.newFilename() | ||
const { currentFilename, stream, closer } = this | ||
if (newFilename !== this.currentFilename) { | ||
if (this.currentFilename && this.stream) { | ||
this.stream.close() | ||
const streamErrorHandler = this.streamErrorHandlers.get(this.currentFilename) | ||
if (newFilename !== currentFilename) { | ||
if (currentFilename && stream && closer) { | ||
closer.remove() | ||
stream.end() | ||
const streamErrorHandler = this.streamErrorHandlers.get(currentFilename) | ||
if (streamErrorHandler) { | ||
this.stream.removeListener('error', streamErrorHandler) | ||
this.streamErrorHandlers.delete(this.currentFilename) | ||
stream.removeListener('error', streamErrorHandler) | ||
this.streamErrorHandlers.delete(currentFilename) | ||
} | ||
if (!HAS_DESTROY) { | ||
this.streams.delete(this.currentFilename) | ||
} | ||
} | ||
@@ -160,10 +179,25 @@ | ||
if (HAS_DESTROY) { | ||
const newStreamCancelFinisher = finished(newStream, () => { | ||
const newTimer = interval(FileTimestampStream.CLOSE_UNUSED_FILE_AFTER, () => { | ||
if (newFilename !== this.newFilename()) { | ||
newTimer.remove() | ||
this.closers.delete(newFilename) | ||
newStream.end() | ||
} | ||
}) | ||
this.closer = closer | ||
this.closers.set(newFilename, newTimer) | ||
const newStreamCancelFinisher = finished(newStream, () => { | ||
newTimer.remove() | ||
this.closers.delete(newFilename) | ||
// tslint:disable-next-line:strict-type-predicates | ||
if (typeof newStream.destroy === 'function') { | ||
newStream.destroy() | ||
this.streamCancelFinishers.delete(newFilename) | ||
this.streams.delete(newFilename) | ||
}) | ||
this.streamCancelFinishers.set(newFilename, newStreamCancelFinisher) | ||
} | ||
} | ||
this.streamCancelFinishers.delete(newFilename) | ||
this.streams.delete(newFilename) | ||
}) | ||
this.streamCancelFinishers.set(newFilename, newStreamCancelFinisher) | ||
@@ -170,0 +204,0 @@ this.currentFilename = newFilename |
@@ -10,3 +10,2 @@ { | ||
"module": "commonjs", | ||
"noImplicitAny": true, | ||
"noImplicitReturns": true, | ||
@@ -13,0 +12,0 @@ "noUnusedLocals": true, |
22605
399
128
4
+ Addedtimers-obj@^0.2.1
+ Addedtimers-obj@0.2.2(transitive)