Security News
Supply Chain Attack Detected in Solana's web3.js Library
A supply chain attack has been detected in versions 1.95.6 and 1.95.7 of the popular @solana/web3.js library.
@logdna/tail-file
Advanced tools
@logdna/tail-file is an npm package designed to efficiently tail files, meaning it can read and monitor the end of a file for new data. This is particularly useful for log files where new entries are continuously appended. The package provides a simple API to start, stop, and manage the tailing process, making it suitable for real-time log monitoring and processing.
Basic Tailing
This feature allows you to start tailing a file and listen for new lines being added to the file. The 'line' event is emitted whenever a new line is appended to the file.
const { TailFile } = require('@logdna/tail-file');
const tail = new TailFile('/path/to/your/file.log');
(async () => {
try {
await tail.start();
tail.on('line', (line) => {
console.log('New line:', line);
});
} catch (err) {
console.error('Error starting tail:', err);
}
})();
Handling Errors
This feature demonstrates how to handle errors that may occur during the tailing process. The 'error' event is emitted whenever an error is encountered.
const { TailFile } = require('@logdna/tail-file');
const tail = new TailFile('/path/to/your/file.log');
tail.on('error', (err) => {
console.error('Error:', err);
});
(async () => {
try {
await tail.start();
} catch (err) {
console.error('Error starting tail:', err);
}
})();
Stopping the Tail
This feature shows how to stop the tailing process after a certain period. The 'quit' method is used to stop tailing the file.
const { TailFile } = require('@logdna/tail-file');
const tail = new TailFile('/path/to/your/file.log');
(async () => {
try {
await tail.start();
// Stop tailing after 10 seconds
setTimeout(async () => {
await tail.quit();
console.log('Stopped tailing');
}, 10000);
} catch (err) {
console.error('Error starting tail:', err);
}
})();
The 'tail' package is a simple library for tailing a file in Node.js. It provides basic functionality to read new lines appended to a file. Compared to @logdna/tail-file, it offers fewer features and less flexibility in handling errors and managing the tailing process.
The 'tail-stream' package is another alternative for tailing files in Node.js. It provides a stream interface for reading new lines from a file. While it is more flexible than the 'tail' package, it still lacks some of the advanced features and error handling capabilities provided by @logdna/tail-file.
The 'chokidar' package is a file watcher library that can be used to monitor changes in files and directories. While it is not specifically designed for tailing files, it can be used to achieve similar functionality by watching for changes and reading new data. It offers a broader range of features for file system monitoring compared to @logdna/tail-file.
At LogDNA, consuming log files and making them searchable is what we do!
It all starts with the ability to efficiently watch log files on a local
host and send new lines up to the LogDNA service. This Node.js class provides
functionality like Unix's tail -f
command, and we use it in our
agents to get the job done. Of course, anything needing tail
functionality
in Node.js could also benefit from using this.
Readable
stream, which is efficient
and flexible in terms of being able to pipe
to other streams or consume via events.npm install @logdna/tail-file
This package works with the following supported versions. Other versions might work, but the ones listed here are officially tested.
If backward-compatibility is lost over time, a major
version will be released supporting
the latest version, and deprecating incompatible versions. Users are then encouraged to try
previous versions of this package for those deprecated versions of Node.js.
Node.js versions: 18
, 20
Instantiate an instance by passing the full path of a file to tail.
This will return a stream that can be piped to other streams or consumed
via data
events. To begin the tailing, call the start
method.
data
eventsconst TailFile = require('@logdna/tail-file')
const tail = new TailFile('/path/to/your/logfile.txt', {encoding: 'utf8'})
.on('data', (chunk) => {
console.log(`Recieved a utf8 character chunk: ${chunk}`)
})
.on('tail_error', (err) => {
console.error('TailFile had an error!', err)
})
.on('error', (err) => {
console.error('A TailFile stream error was likely encountered', err)
})
.start()
.catch((err) => {
console.error('Cannot start. Does the file exist?', err)
})
pipe
This example is more realistic. It pipes the output to a transform stream
which breaks the data up by newlines, emitting its own data
event for
every line.
const TailFile = require('@logdna/tail-file')
const split2 = require('split2') // A common and efficient line splitter
const tail = new TailFile('/path/to/your/logfile.txt')
tail
.on('tail_error', (err) => {
console.error('TailFile had an error!', err)
throw err
})
.start()
.catch((err) => {
console.error('Cannot start. Does the file exist?', err)
throw err
})
// Data won't start flowing until piping
tail
.pipe(split2())
.on('data', (line) => {
console.log(line)
})
readline
This is an easy way to get a "line splitter" by using Node.js core modules.
For tailing files with high throughput, an official Transform
stream is
recommended since it will edge out readline
slightly in performance.
const readline = require('readline')
const TailFile = require('@logdna/tail-file')
async function startTail() {
const tail = new TailFile('./somelog.txt')
.on('tail_error', (err) => {
console.error('TailFile had an error!', err)
})
try {
await tail.start()
const linesplitter = readline.createInterface({
input: tail
})
linesplitter.on('line', (line) => {
console.log(line)
})
} catch (err) {
console.error('Cannot start. Does the file exist?', err)
}
}
startTail().catch((err) => {
process.nextTick(() => {
throw err
})
})
TailFile
will call flush()
when quit()
is called. Therefore, to exit cleanly,
one must simply await the quit
call. If the implementation wishes to keep track of
the last position read from the file (for resuming in the same spot later, for example),
then a simple listener can be added to always track the file position. That way, when
quit()
is called, it will get properly updated.
const TailFile = require('@logdna/tail-file')
let position // Can be used to resume the last position from a new instance
const tail = new TailFile('./somelog.txt')
process.on('SIGINT', () => {
tail.quit()
.then(() => {
console.log(`The last read file position was: ${position}`)
})
.catch((err) => {
process.nextTick(() => {
console.error('Error during TailFile shutdown', err)
})
})
})
tail
.on('flush', ({lastReadPosition}) => {
position = lastReadPosition
})
.on('data', (chunk) => {
console.log(chunk.toString())
})
.start()
.catch((err) => {
console.error('Cannot start. Does the file exist?', err)
throw err
})
TailFile
is a Readable
stream, so it can emit any events from that
superclass. Additionally, it will emit the following custom events.
'flush'
This event is emitted when the underlying stream is done being read.
If backpressure is in effect, then _read()
may be called multiple
times until it's flushed, so this event signals the end of the process.
It is used primarily in shutdown to make sure the data is exhausted,
but users may listen for this event if the relative "read position" in the
file is of interest. For example, the lastReadPosition
may be persisted to memory
or database for resuming tail-file
on a separate execution without missing
any lines or duplicating them.
'renamed'
This event is emitted when a file with the same name is found, but has a different inode than the previous poll. Commonly, this happens during a log rotation.
'retry'
If a file that was successfully being tailed goes away, TailFile
will
try for maxPollFailures
to re-poll the file. For each of those retries,
this event is emitted for informative purposes. Typically, this could happen
if log rolling is occurring manually, or timed in a way where the poll happens
during the time in which the "new" filename is not yet created.
'tail_error'
When an error happens that is specific to TailFile
, it cannot emit an error
event
without causing the main stream to end (because it's a Readable
implementation).
Therefore, if an error happens in a place such as reading the underlying file
resource, a tail_error
event will be emitted instead.
'truncated'
If a file is shortened or truncated without moving or renaming the file,
TailFile
will assume it to be a new file, and it will start consuming
lines from the beginning of the file. This event is emitted for informational
purposes about that behavior.
Readable
event)TailFile
implements a Readable
stream, so it may also emit these events. The most common ones are close
(when TailFile
exits), or data
events from the stream.
new TailFile(filename[, options])
filename
<String>
- The filename to tail.
Poll errors do not happen until start
is called.options
<Object>
- Optional
pollFileIntervalMs
<Number>
- How often to poll filename
for changes.
Default: 1000
mspollFailureRetryMs
<Number>
- After a polling error (ENOENT?), how long to
wait before retrying. Default: 200
msmaxPollFailures
<Number>
- The number of times to retry a failed poll
before exiting/erroring. Default: 10
times.readStreamOpts
<Object>
- Options to pass to the
fs.createReadStream
function. This is used for reading bytes
that have been added to filename
between every poll.startPos
<Number>
- An integer representing the inital read position in
the file. Useful for reading from 0
. Default: null
(start tailing from EOF)Readable
superclass
constructor of TailFile
<TypeError>
|<RangeError>
if parameter validation failsTailFile
, which is a Readable
streamInstantiating TailFile
will return a readable stream, but nothing will happen
until start()
is called. After that, follow node's standard procedure to
get the stream into flowing mode. Typically, this means using
pipe
or attaching data
listeners to the readable stream.
As the underlying filename
is polled for changes, it will call
fs.createReadStream
to efficiently read the changed bytes since the last poll.
To control the options of that stream, the key-values in readStreamOpts
will
be passed to the fs.createReadStream
constructor. Similarly, options for
controlling TailFile
's' stream can be passed in via options
, and they will
get passed through to the Readable
's super()
constructor.
Useful settings such as encoding: 'utf8'
can be used this way.
tail.start()
<Promise>
- Resolves after the file is polled successfullyfilename
is not foundCalling start()
begins the polling of filename
to watch for added/changed bytes.
start()
may be called before or after data is set up to be consumed with a
data
listener or a pipe
. Standard node stream rules apply, which say
that data will not flow through the stream until it's consumed.
tail.quit()
<Promise>
- Resolves after flush
is called and streams are closed.close
when the parent Readstream
is ended.This function calls flush
, then closes all streams and exits cleanly. The parent TailFile
stream will be
properly ended by pushing null
, therefore an end
event may be emitted as well.
Using "file watcher" events don't always work across different operating systems,
therefore the most effective way to "tail" a file is to continuously poll
it for changes and read those changes when they're detected.
Even Unix's tail -f
command works similarly.
Once start()
is called, TailFile
will being this polling process. As changes
are detected through a .size
comparison, it uses fs.openReadStream
to
efficiently read to the end of the file using async/await iterators.
This allows backpressure to be supported throughout the process.
TailFile
keeps a FileHandle
open for the filename
, which is attached to an
inode. If log rolling happens, TailFile
uses the FileHandle
to read the rest of the
"old" file before starting the process from the beginning of the newly-created file.
This ensures that no data is lost due to the rolling/renaming of filename
.
This functionality assumes that filename
is re-created with the same name,
otherwise an error is emitted if filename
does not re-appear.
Because TailFile
won't be consumed until it is in a reading mode,
this may cause backpressure to be enacted. In other words, if .start()
is called,
but pipe
or data events are not immediately set up, TailFile
may encounter
backpressure if its push()
calls exceed the high water mark.
Backpressure can also happen if TailFile
becomes unpiped.
In these cases, TailFile
will stop polling and wait until data is flowing before
polling resumes.
If polling is off during backpressure, TailFile
can handle
a single log roll or rename during backpressure, but if
the log is renamed more than once, there will most likely be data loss, as polling for
changes will be off.
This is an extrememly unlikely edge case, however we recommend consuming the TailFile
stream almost immediately upon creation.
This project is open-sourced, and accepts PRs from the public for bugs or feature enhancements. These are the guidelines for contributing:
Fixes: #5
FAQs
A node.js version of unix's `tail -f` command
We found that @logdna/tail-file demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 4 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
A supply chain attack has been detected in versions 1.95.6 and 1.95.7 of the popular @solana/web3.js library.
Research
Security News
A malicious npm package targets Solana developers, rerouting funds in 2% of transactions to a hardcoded address.
Security News
Research
Socket researchers have discovered malicious npm packages targeting crypto developers, stealing credentials and wallet data using spyware delivered through typosquats of popular cryptographic libraries.