Skip to content

Commit

Permalink
Merge pull request #67 from assaf/web-tail
Browse files Browse the repository at this point in the history
Web tail
  • Loading branch information
typicode committed Mar 16, 2015
2 parents 521c212 + 44d65dc commit 35f0a52
Show file tree
Hide file tree
Showing 4 changed files with 247 additions and 121 deletions.
135 changes: 19 additions & 116 deletions src/cli/commands/tail.js
Original file line number Diff line number Diff line change
@@ -1,125 +1,28 @@
var fs = require('fs')
var chalk = require('chalk')
var path = require('path')
var pathToHost = require('../utils/path-to-host')
var config = require('../../config.js')
var chalk = require('chalk')
var Tail = require('../../daemon/utils/tail')
var pathToHost = require('../utils/path-to-host')


// Number of lines to show from end of log.
var INITIAL_LINES = 10


// Tail the end of the stream from the previous position. When done, go back
// to watching the file for changes.
function tailStream(filename, prefix, position) {
var streamOptions = {
start: position,
encoding: 'utf8'
}
var stream = fs.createReadStream(filename, streamOptions)

var lastLine = ''
stream.on('data', function(chunk) {
var buffer = lastLine + chunk
var lines = buffer.split(/\n/)
lastLine = lines.pop()
for (var i in lines) {
var line = lines[i] + '\n'
process.stdout.write(prefix + line)
position += line.length
}
})

stream.on('end', function() {
watchFile(filename, prefix, position)
})
stream.on('error', function(error) {
watchFile(filename, prefix, position)
})
}


// Watch file for changes. When Katon appends to file we get a change event
// and read from previous position forward.
function watchFile(filename, prefix, position) {
try {
var watcher = fs.watch(filename, function(event) {
var stats = fs.statSync(filename)
if (stats.size < position) {
// File got truncated need to adjust position to new end of file
position = stats.size
} else if (stats.size > position) {
// File has more content, stop watching while we tail the stream
watcher.close()
tailStream(filename, prefix, position)
}
})
} catch (error) {
console.log(chalk.red("Cannot tail %s, start server and try again"), filename)
process.exit(1)
}
}


// This function dumps the end of the existing log file, and then starts
// watching for changes.
function tailFile(filename, prefix) {
var stream = fs.createReadStream(filename, { encoding: 'utf8' })
var lines = []
var buffer = ''
var position = 0
stream.on('data', function(chunk) {
buffer += chunk
// Parse stream into lines and keep the last INITIAL_LINES in memory.
// Make sure position points to the last byte in the file, or the first
// byte of the last incomplete line.
var match
while (match = buffer.match(/^.*\n/)) {
var line = match[0]
lines.push(line)
position += line.length
buffer = buffer.slice(line.length)
}
lines = lines.slice(-INITIAL_LINES)
})
stream.on('end', function() {
// Dump the tail of the log to the console and start watching for changes.
//for (var i = 0; i < lines.length ; ++i)
for (var i in lines)
process.stdout.write(prefix + lines[i])
watchFile(filename, prefix, position)
})
stream.on('error', function(error) {
console.log(chalk.red("Cannot tail %s, start server and try again"), filename)
process.exit(1)
})
}


// Tail all log files from the logs directory
function tailAllLogFiles()
{
fs.readdirSync(config.logsDir)
.filter(function(filename) {
return /\.log$/.test(filename)
})
.map(function(filename) {
return config.logsDir + '/' + filename
module.exports = function(args) {
var host = args[0] || pathToHost(process.cwd())
var tail = new Tail(host, INITIAL_LINES);
tail
.on('line', function(prefix, line) {
if (prefix)
process.stdout.write(chalk.blue('[' + prefix + '] '))
process.stdout.write(line + '\n')
})
.forEach(function(filename) {
var basename = path.basename(filename, '.log')
var prefix = chalk.blue('[' + basename + '] ')
tailFile(filename, prefix)
.on('error', function(error) {
process.stderr.write(chalk.red(error.message) + '\n')
process.exit(1)
})
}

tail.start()

module.exports = function(args) {
var host = args[0] || pathToHost(process.cwd())
var logFile = config.logsDir + '/' + host + '.log'

if (host == 'all')
tailAllLogFiles()
else
tailFile(logFile, '')
// Keep process running until Ctrl+C
setInterval(function() {
}, 1000)
}

39 changes: 35 additions & 4 deletions src/daemon/http-router.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ var address = require('network-address')
var chalk = require('chalk')
var procs = require('./procs')
var render = require('./utils/render')
var Tail = require('./utils/tail')
var timer = require('./utils/timer')
var util = require('util')

Expand Down Expand Up @@ -73,6 +74,31 @@ function startProc(host) {
return proc
}

function tailSSE(res) {
res.writeHead(200, {
'Content-Type': 'text/event-stream; charset=utf-8',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
});
var tail = new Tail('all', 10)
tail
.on('line', function(prefix, line) {
var message = {
prefix: prefix,
line: line.replace(/\[\d{2}m/g, '')
}
res.write('event: line\ndata: ' + JSON.stringify(message) + '\n\n');
})
.on('error', function(error) {
log('all', 'Cannot tail logs', error)
})
res.on('close', function() {
tail.stop()
})
tail.start()
}


module.exports.createServer = function() {

var server = http.createServer()
Expand Down Expand Up @@ -136,10 +162,15 @@ module.exports.createServer = function() {
// Render katon home
var domain = getDomainId(host)
if (domain === 'index' || domain === 'katon') {
return res.end(render('200.html', {
procs: procs.list,
ip: address()
}))
if (req.url == '/tail') {
tailSSE(res)
return
} else {
return res.end(render('200.html', {
procs: procs.list,
ip: address()
}))
}
}

// Verify host is set and valid
Expand Down
22 changes: 21 additions & 1 deletion src/daemon/templates/200.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,24 @@
<% }) %>
</table>

<p><em>* xip.io is a wildcard DNS service, you can use these addresses to access your development web server from devices on your local network, like iPads, iPhones, and other computers.</em></p>
<p><em>* xip.io is a wildcard DNS service, you can use these addresses to access your development web server from devices on your local network, like iPads, iPhones, and other computers.</em></p>
<pre id="tail" class="pre-scrollable"></pre>

<script>
var tail = document.getElementById('tail');
var source = new EventSource('/tail');
source.addEventListener('line', function(event) {
var message = JSON.parse(event.data);
var entry = document.createElement('div')
var prefix = document.createElement('span')
prefix.textContent = message.prefix + ': '
prefix.className = 'text-primary'
entry.appendChild(prefix)
var line = document.createElement('span')
line.className = 'text-nowrap'
line.textContent = message.line
entry.appendChild(line)
tail.appendChild(entry)
tail.scrollTop = entry.offsetTop
});
</script>
Loading

0 comments on commit 35f0a52

Please sign in to comment.