pixl-logger
Advanced tools
Comparing version 1.0.1 to 1.0.2
218
logger.js
@@ -6,3 +6,11 @@ // Generic Logger Class for Node.JS | ||
var fs = require('fs'); | ||
var zlib = require('zlib'); | ||
var Path = require('path'); | ||
var os = require('os'); | ||
var async = require('async'); | ||
var mkdirp = require('mkdirp'); | ||
var glob = require('glob'); | ||
var Class = require("pixl-class"); | ||
var Tools = require("pixl-tools"); | ||
@@ -21,3 +29,3 @@ module.exports = Class.create({ | ||
if (!this.args.hostname) { | ||
this.args.hostname = ''; | ||
this.args.hostname = os.hostname().toLowerCase(); | ||
} | ||
@@ -45,6 +53,7 @@ }, | ||
delete args.now; | ||
var dargs = get_date_args(now); | ||
var dargs = Tools.getDateArgs(now); | ||
// import args into object | ||
for (var key in args) this.args[key] = args[key]; | ||
// for (var key in dargs) this.args[key] = dargs[key]; | ||
@@ -54,3 +63,3 @@ // set automatic column values | ||
this.args.epoch = Math.floor( this.args.hires_epoch ); | ||
this.args.date = dargs.yyyy + '-' + dargs.mm + '-' + dargs.dd + ' ' + dargs.hh + ':' + dargs.mi + ':' + dargs.ss; | ||
this.args.date = dargs.yyyy_mm_dd + ' ' + dargs.hh_mi_ss; | ||
@@ -78,4 +87,10 @@ // support json 'data' arg | ||
// file path may have placeholders, expand these if necessary | ||
var path = this.path; | ||
if (path.indexOf('[') > -1) { | ||
path = Tools.substitute( path, this.args ); | ||
} | ||
// append to log | ||
fs.appendFile(this.path, line); | ||
fs.appendFile(path, line); | ||
@@ -117,38 +132,163 @@ // echo to console if desired | ||
}); | ||
} | ||
}, | ||
rotate: function() { | ||
// rotate any log file atomically (defaults to our own file) | ||
// 2 arg convention: dest_path, callback | ||
// 3 arg convention: log_file, dest_path, callback | ||
var log_file = ''; | ||
var dest_path = ''; | ||
var callback = null; | ||
if (arguments.length == 3) { | ||
log_file = arguments[0]; | ||
dest_path = arguments[1]; | ||
callback = arguments[2]; | ||
} | ||
else if (arguments.length == 2) { | ||
dest_path = arguments[0]; | ||
callback = arguments[1]; | ||
} | ||
else throw new Error("Wrong number of arguments to rotate()"); | ||
if (!log_file) log_file = this.path; | ||
if (log_file.indexOf('[') > -1) { | ||
log_file = Tools.substitute( log_file, this.args ); | ||
} | ||
// if dest path ends with a slash, add src filename + date/time stamp | ||
if (dest_path.match(/\/$/)) { | ||
var dargs = Tools.getDateArgs( Tools.timeNow() ); | ||
dest_path += Path.basename(log_file); | ||
dest_path += '.' + (dargs.yyyy_mm_dd + '-' + dargs.hh_mi_ss).replace(/\W+/g, '-'); | ||
dest_path += '.' + Tools.generateUniqueID(16) + Path.extname(log_file); | ||
} | ||
// try fs.rename first | ||
fs.rename(log_file, dest_path, function(err) { | ||
if (err && (err.code == 'EXDEV')) { | ||
// dest path crosses fs boundary, gotta rename + copy + rename + delete | ||
// first, perform local rename in source log directory | ||
var src_temp_file = log_file + '.' + Tools.generateUniqueID(32) + '.tmp'; | ||
fs.rename(log_file, src_temp_file, function(err) { | ||
if (err) { | ||
if (callback) callback(err); | ||
return; | ||
} | ||
// copy src temp to dest temp file, then rename it | ||
var dest_temp_file = dest_path + '.' + Tools.generateUniqueID(32) + '.tmp'; | ||
var inp = fs.createReadStream(src_temp_file); | ||
var outp = fs.createWriteStream(dest_temp_file); | ||
if (callback) inp.on('error', callback ); | ||
if (callback) outp.on('error', callback ); | ||
inp.on('end', function() { | ||
// final rename | ||
fs.rename(dest_temp_file, dest_path, function(err) { | ||
if (err) { | ||
if (callback) callback(err); | ||
return; | ||
} | ||
// all done, delete src temp file | ||
fs.unlink(src_temp_file, function(err) { | ||
if (callback) callback(err); | ||
}); | ||
}); | ||
} ); | ||
inp.pipe( outp ); | ||
} ); // src rename | ||
} // EXDEV | ||
else { | ||
// rename worked, or other error | ||
if (callback) callback(err); | ||
} | ||
} ); // fs.rename | ||
}, | ||
archive: function(src_spec, dest_path, epoch, callback) { | ||
// archive one or more log files, can use glob spec (defaults to our file). | ||
// dest path may use placeholders: [yyyy], [mm], [dd], [hh], [filename], [hostname], etc. | ||
// creates dest dirs as needed. | ||
// if dest path ends in .gz, archives will be compressed. | ||
var self = this; | ||
if (!src_spec) src_spec = this.path; | ||
if (!callback) callback = function() {}; | ||
// fill date/time placeholders | ||
var dargs = Tools.getDateArgs( epoch ); | ||
for (var key in dargs) self.args[key] = dargs[key]; | ||
glob(src_spec, {}, function (err, files) { | ||
// got files | ||
if (files && files.length) { | ||
async.eachSeries( files, function(src_file, callback) { | ||
// foreach file | ||
if (err) return callback(err); | ||
// add filename to args | ||
self.args.filename = Path.basename(src_file).replace(/\.\w+$/, ''); | ||
// construct final path | ||
var dest_file = Tools.substitute( dest_path, self.args ); | ||
// rename local log first | ||
var src_temp_file = src_file + '.' + Tools.generateUniqueID(32) + '.tmp'; | ||
fs.rename(src_file, src_temp_file, function(err) { | ||
if (err) { | ||
return callback( new Error("Failed to rename: " + src_file + " to: " + src_temp_file + ": " + err) ); | ||
} | ||
// create dest dirs as necessary | ||
mkdirp(Path.dirname(dest_file), function (err) { | ||
if (err) { | ||
return callback( new Error("Failed to make directories for: " + dest_file + ": " + err) ); | ||
} | ||
if (dest_file.match(/\.gz$/i)) { | ||
// gzip the log archive | ||
var gzip = zlib.createGzip(); | ||
var inp = fs.createReadStream( src_temp_file ); | ||
var outp = fs.createWriteStream( dest_file, {flags: 'a'} ); | ||
inp.on('error', callback); | ||
outp.on('error', callback); | ||
inp.on('end', function() { | ||
// all done, delete temp file | ||
fs.unlink( src_temp_file, callback ); | ||
} ); | ||
inp.pipe(gzip).pipe(outp); | ||
} // gzip | ||
else { | ||
// straight copy (no compress) | ||
var inp = fs.createReadStream( src_temp_file ); | ||
var outp = fs.createWriteStream( dest_file, {flags: 'a'} ); | ||
inp.on('error', callback); | ||
outp.on('error', callback); | ||
inp.on('end', function() { | ||
// all done, delete temp file | ||
fs.unlink( src_temp_file, callback ); | ||
} ); | ||
inp.pipe( outp ); | ||
} // copy | ||
} ); // mkdirp | ||
} ); // fs.rename | ||
}, callback ); // eachSeries | ||
} // got files | ||
else { | ||
callback( err || new Error("No files found matching: " + src_spec) ); | ||
} | ||
} ); // glob | ||
} // archive() | ||
}); | ||
function get_date_args(thingy) { | ||
// return hash containing year, mon, mday, hour, min, sec | ||
// given epoch seconds | ||
var date = thingy.getFullYear ? thingy : (new Date( thingy * 1000 )); | ||
var args = { | ||
year: date.getFullYear(), | ||
mon: date.getMonth() + 1, | ||
mday: date.getDate(), | ||
hour: date.getHours(), | ||
min: date.getMinutes(), | ||
sec: date.getSeconds(), | ||
msec: date.getMilliseconds() | ||
}; | ||
args.yyyy = args.year; | ||
if (args.mon < 10) args.mm = "0" + args.mon; else args.mm = args.mon; | ||
if (args.mday < 10) args.dd = "0" + args.mday; else args.dd = args.mday; | ||
if (args.hour < 10) args.hh = "0" + args.hour; else args.hh = args.hour; | ||
if (args.min < 10) args.mi = "0" + args.min; else args.mi = args.min; | ||
if (args.sec < 10) args.ss = "0" + args.sec; else args.ss = args.sec; | ||
if (args.hour >= 12) { | ||
args.ampm = 'pm'; | ||
args.hour12 = args.hour - 12; | ||
if (!args.hour12) args.hour12 = 12; | ||
} | ||
else { | ||
args.ampm = 'am'; | ||
args.hour12 = args.hour; | ||
if (!args.hour12) args.hour12 = 12; | ||
} | ||
return args; | ||
}; |
{ | ||
"name": "pixl-logger", | ||
"version": "1.0.1", | ||
"description": "A simple logging class which generates [bracket][delimited] log columns.", | ||
"author": "Joseph Huckaby <jhuckaby@gmail.com>", | ||
"homepage": "https://github.com/jhuckaby/pixl-logger", | ||
"license": "MIT", | ||
"main": "logger.js", | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/jhuckaby/pixl-logger" | ||
}, | ||
"bugs": { | ||
"url": "https://github.com/jhuckaby/pixl-logger/issues" | ||
}, | ||
"keywords": [ | ||
"logging", | ||
"clear" | ||
], | ||
"dependencies": { | ||
"pixl-class": ">=1.0.0" | ||
}, | ||
"devDepenendcies": { | ||
} | ||
} | ||
"name": "pixl-logger", | ||
"version": "1.0.2", | ||
"description": "A simple logging class which generates [bracket][delimited] log columns.", | ||
"author": "Joseph Huckaby <jhuckaby@gmail.com>", | ||
"homepage": "https://github.com/jhuckaby/pixl-logger", | ||
"license": "MIT", | ||
"main": "logger.js", | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/jhuckaby/pixl-logger" | ||
}, | ||
"bugs": { | ||
"url": "https://github.com/jhuckaby/pixl-logger/issues" | ||
}, | ||
"keywords": [ | ||
"logging", | ||
"clear" | ||
], | ||
"dependencies": { | ||
"async": "^1.0.0", | ||
"mkdirp": "^0.5.1 || ^1.0.0", | ||
"glob": "^5.0.0", | ||
"pixl-class": ">=1.0.0", | ||
"pixl-tools": ">=1.0.0" | ||
}, | ||
"devDependencies": {} | ||
} |
@@ -197,4 +197,42 @@ # Overview | ||
## Rotating Logs | ||
To rotate a log file, call the `rotate()` method, passing in a destination filesystem path and a callback. This will atomically move the file to the destination directory/filename, attempting a rename, and falling back to a "copy to temp file + rename" strategy. Example: | ||
```javascript | ||
logger.rotate( '/logs/pickup/myapp.log', function(err) { | ||
if (err) throw err; | ||
} ); | ||
``` | ||
If you omit a filename on the destination path and leave a trailing slash, the source log filename will be appended to it. | ||
You can actually rotate any log file you want by specifying three arguments, with the custom source log file path as the first argument. Example: | ||
```javascript | ||
logger.rotate( '/path/to/logfile.log', '/logs/pickup/otherapp.log', function(err) { | ||
if (err) throw err; | ||
} ); | ||
``` | ||
## Archiving Logs | ||
You can also "archive" logs using the `archive()` method. Archiving differs from rotation in that the log file is atomically copied to a custom location which may contain date/time directories (all auto-created as needed), and then the file is compressed using gzip. You can archive any number of logs at once by using [filesystem glob syntax](https://en.wikipedia.org/wiki/Glob_%28programming%29). Example: | ||
```javascript | ||
var src_spec = '/logs/myapp/*.log'; | ||
var dest_path = '/archives/myapp/[yyyy]/[mm]/[dd]/[filename]-[hh].log.gz'; | ||
var epoch = ((new Date()).getTime() / 1000) - 1800; // 30 minutes ago | ||
logger.archive( src_spec, dest_path, epoch, function(err) { | ||
if (err) throw err; | ||
} ); | ||
``` | ||
This example would find all the log files found in the `/logs/myapp/` directory that end in `.log`, and archive them to destination directory `/archives/myapp/[yyyy]/[mm]/[dd]/`, with a destination filename pattern of `[filename]-[hh].log.gz`. All the bracket-delimited placeholders are expanded using the timestamp provided in the `epoch` variable. The special `[filename]` placeholder expands to the source log filename, sans extension. All directories are created as needed. | ||
# License | ||
The MIT License | ||
Copyright (c) 2015 Joseph Huckaby | ||
@@ -201,0 +239,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
19000
241
256
5
+ Addedasync@^1.0.0
+ Addedglob@^5.0.0
+ Addedmkdirp@^0.5.1 || ^1.0.0
+ Addedpixl-tools@>=1.0.0
+ Addedasync@1.5.22.6.4(transitive)
+ Addedbalanced-match@1.0.2(transitive)
+ Addedbrace-expansion@1.1.11(transitive)
+ Addedconcat-map@0.0.1(transitive)
+ Addederrno@0.1.7(transitive)
+ Addedglob@5.0.15(transitive)
+ Addedinflight@1.0.6(transitive)
+ Addedinherits@2.0.4(transitive)
+ Addedlodash@4.17.21(transitive)
+ Addedminimatch@3.1.2(transitive)
+ Addedmkdirp@1.0.4(transitive)
+ Addedonce@1.4.0(transitive)
+ Addedpath-is-absolute@1.0.1(transitive)
+ Addedpicomatch@4.0.1(transitive)
+ Addedpixl-tools@1.1.5(transitive)
+ Addedprr@1.0.1(transitive)
+ Addedwrappy@1.0.2(transitive)