├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── alpine.js ├── buffer.js ├── examples └── broken-links.js ├── package.json ├── stringreader.js └── test ├── test_alpine.js └── test_buffer.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .idea 3 | .vscode 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #Alpine 2 | 3 | `alpine` is a parser for Apache mod_log log files. It supports the three most common log formats (the Common Log Format, 4 | the Common Log Format with a vhost field and the Combined log format) 5 | and also allows you to specify custom log formats by passing it the LogFormat string used to generate the log file you want parsed. 6 | 7 | ## Predefined log formats 8 | 9 | Alpine has these three predefined log formats: 10 | 11 | - Alpine.LOGFORMATS.COMBINED 12 | - Alpine.LOGFORMATS.CLF 13 | - Alpine.LOGFORMATS.CLF_VHOST 14 | 15 | that can be passed as arguments to the constructor or configured with the .setLogFormat() method. 16 | 17 | The default log format is Alpine.LOGFORMATS.COMBINED. 18 | 19 | ## Examples 20 | 21 | ### Parse from string using custom log format 22 | The simplest (if not all that useful) use case is 23 | ```js 24 | var Alpine = require('alpine'); 25 | var alpine = new Alpine("%h %s %B"); 26 | var data = alpine.parseLine("www.brain-salad.com 403 4321"); 27 | console.log(data); 28 | ``` 29 | 30 | which produces 31 | 32 | ```js 33 | { 34 | originalLine: 'www.brain-salad.com 403 4321', 35 | remoteHost: 'www.brain-salad.com', 36 | status: '403', 37 | size: '4321' 38 | } 39 | ``` 40 | 41 | ### Parse file in combined log format with callbacks 42 | ```js 43 | var fs = require('fs') 44 | var Alpine = require('alpine'); 45 | var alpine = new Alpine(); 46 | alpine.parseReadStream(fs.createReadStream('access_log', {encoding: "utf8"}), 47 | function(data) { 48 | console.log("Status: " + data.status + ", request: " + data.request); 49 | }); 50 | ``` 51 | 52 | ### Use streams 53 | Alpine supports duplex streaming, but the stream it reads from must be a per-line stream, as implemented by the byline module. 54 | 55 | - Alpine().getObjectStream() returns a duplex stream that will write parsed objects. 56 | - Alpine().getStringStream() returns a duplex stream that will write the same parsed objects, but serialized using JSON.stringify() 57 | 58 | ```js 59 | var fs = require('fs') 60 | var byline = require('byline'); 61 | var Alpine = require('alpine'); 62 | byline.createStream(fs.createReadStream('access_log', {encoding: "utf8"})) 63 | .pipe(new Alpine().getStringStream()) 64 | .pipe(fs.createWriteStream("access.out")); 65 | ``` 66 | 67 | ## Restrictions 68 | 69 | Alpine assumes that the log format contains fields, quotation marks (surrounding fields) and whitespace. Further literal text is not supported. 70 | 71 | Alpine will probably only work with log files created by Apache HTTPD version 2.0.46 and later - earlier versions logged the contents 72 | of the %r, %i and %o fields without quoting the data, making logs irregular and unpredictable. 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /alpine.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Alpine, the Apache Log Parser 4 | * 5 | * Created by blarsen on 02.10.14. 6 | */ 7 | 8 | "use strict"; 9 | 10 | var Buffer = require('./buffer'); 11 | 12 | var byline = require('byline'); 13 | var _ = require("underscore.string"); 14 | var through2 = require('through2'); 15 | 16 | var Alpine = function (logformat) { 17 | 18 | this.setLogFormat = setLogFormat; 19 | this.getLogFormat = getLogFormat; 20 | this.parseLine = parseLine; 21 | this.getObjectStream = getObjectStream; 22 | this.getStringStream = getStringStream; 23 | this.parseReadStream = parseReadStream; 24 | 25 | if (logformat) 26 | this.setLogFormat(logformat); 27 | else 28 | this.setLogFormat(Alpine.LOGFORMATS.COMBINED); 29 | 30 | }; 31 | 32 | function getObjectStream() { 33 | var thisAlpine = this; 34 | return through2.obj(function(chunk, enc, callback) { 35 | var data = thisAlpine.parseLine(chunk); 36 | this.push(data); 37 | callback(); 38 | }); 39 | } 40 | 41 | function getStringStream() { 42 | var thisAlpine = this; 43 | return through2.obj(function(chunk, enc, callback) { 44 | var data = thisAlpine.parseLine(chunk); 45 | this.push(JSON.stringify(data)); 46 | callback(); 47 | }); 48 | } 49 | 50 | function parseReadStream(stream, callback) { 51 | var thisAlpine = this; 52 | var lineStream = byline.createStream(stream); 53 | lineStream.pipe(through2.obj(function(chunk, enc, t2callback) { 54 | var data = thisAlpine.parseLine(chunk.toString()); 55 | callback(data); 56 | t2callback(); 57 | })) 58 | } 59 | 60 | function getLogFormat() { 61 | return this.logformat; 62 | } 63 | 64 | function setLogFormat(logformat) { 65 | this.logformat = logformat; 66 | this.formatfields = parseLogFormat(logformat); 67 | } 68 | 69 | function parseLine(line) { 70 | var result = { 71 | originalLine: line 72 | }; 73 | 74 | var buf = new Buffer(line, 0); 75 | 76 | this.formatfields.forEach(function(field) { 77 | buf.skipSpaces(); 78 | var val; 79 | if (field.isQuoted) { 80 | if (!(buf.lookingAt() === '"')) 81 | throw new Error("Field defined as quoted was not quoted"); 82 | buf.skip(); 83 | val = buf.getUpto('"'); 84 | buf.skip(1); 85 | buf.skipSpaces(); 86 | } else if (field.isDate) { 87 | if (!(buf.lookingAt() === '[')) 88 | throw new Error("Time field is not enclosed in brackets"); 89 | buf.skip(); 90 | val = buf.getUpto(']'); 91 | buf.skip(1); 92 | buf.skipSpaces(); 93 | } else { 94 | val = buf.getUpto(' '); 95 | } 96 | result[field.name] = val; 97 | }); 98 | 99 | return result; 100 | } 101 | 102 | function parseLogFormat(logformat) { 103 | var fields = []; 104 | var buf = new Buffer(logformat, 0); 105 | while (buf.hasMore()) { 106 | buf.skipSpaces(); 107 | var field = buf.getUpto(" "); 108 | var isQuoted = field[0] === '"'; 109 | field = stripQuotes(field); 110 | 111 | // Check that this is a field definition (starting with %) and remove the prefix 112 | if (!(field[0] === "%")) 113 | throw new Error("Field does not start with %: "+field); 114 | field = field.substring(1); 115 | 116 | // Remove modifiers 117 | if (field.indexOf("{") > 0) { 118 | field = field.replace(/^[0-9!]+/, ""); 119 | } 120 | field = field.replace(/[<>]/g, ""); 121 | 122 | var fieldName = FIELDS[field]; 123 | 124 | var fieldLetter = field; 125 | 126 | // Handle parameterized fields 127 | if (field.indexOf('{') >= 0) { 128 | var matches = (/{(.*)}(.*)/).exec(field); 129 | var value = matches[1]; 130 | fieldLetter = matches[2]; 131 | if (!PARAMFIELDS[fieldLetter]) 132 | throw new Error("The field "+fieldLetter+" should not be parameterized"); 133 | fieldName = PARAMFIELDS[fieldLetter] + ' ' + value; 134 | } 135 | 136 | if (!FIELDS[fieldLetter]) 137 | throw new Error("Unknown log format field "+fieldLetter); 138 | fields.push({ 139 | field: field, 140 | name: fieldName, 141 | isQuoted: isQuoted, 142 | isDate: fieldLetter === 't' 143 | }); 144 | } 145 | return fields; 146 | } 147 | 148 | function stripQuotes(text) { 149 | if ((_.startsWith(text, '"') && _.endsWith(text, '"')) 150 | || (_.startsWith(text, '[')) && _.endsWith(text, ']')) 151 | return text.substr(1, text.length-2); 152 | return text; 153 | } 154 | 155 | Alpine.LOGFORMATS = { 156 | COMBINED: "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"", 157 | CLF: "%h %l %u %t \"%r\" %>s %b", 158 | CLF_VHOST: "%v %h %l %u %t \"%r\" %>s %b" 159 | }; 160 | 161 | var FIELDS = { 162 | 'a': 'remoteIP', 163 | 'A': 'localIP', 164 | 'B': 'size', 165 | 'b': 'sizeCLF', 166 | 'D': 'serveTime', 167 | 'f': 'filename', 168 | 'h': 'remoteHost', 169 | 'H': 'requestProtocol', 170 | 'k': 'keepaliveRequests', 171 | 'l': 'logname', 172 | 'm': 'requestMethod', 173 | 'p': 'port', 174 | 'P': 'pid', 175 | 'q': 'queryString', 176 | 'r': 'request', 177 | 'R': 'responseHandler', 178 | 's': 'status', 179 | 't': 'time', 180 | 'T': 'serveTime', 181 | 'u': 'remoteUser', 182 | 'U': 'urlPath', 183 | 'v': 'canonicalServerName', 184 | 'V': 'serverName', 185 | 'X': 'connectionStatus', 186 | 'I': 'bytesReceived', 187 | 'O': 'bytesSent', 188 | 'C': 'cookie', 189 | 'e': 'environment', 190 | 'i': 'requestHeader', 191 | 'n': 'note', 192 | 'o': 'responseHeader', 193 | '^ti': 'requestTrailerLine', 194 | '^to': 'responseTrailerLine' 195 | }; 196 | 197 | var PARAMFIELDS = { 198 | "c": "Cookie", 199 | "e": "Environment", 200 | "i": "RequestHeader", 201 | "n": "Note", 202 | "o": "ResponseHeader", 203 | "p": "Port", 204 | "P": "PID", 205 | "t": "Time", 206 | '^ti': 'RequestTrailerLine', 207 | '^to': 'ResponseTrailerLine' 208 | }; 209 | 210 | module.exports = Alpine; 211 | -------------------------------------------------------------------------------- /buffer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by blarsen on 03.10.14. 3 | */ 4 | 5 | "use strict"; 6 | 7 | var Buffer = function(data, index) { 8 | this.data = data; 9 | this.index = index; 10 | 11 | this.skipSpaces = function() { 12 | while (this.hasMore() && this.data[this.index] == ' ') 13 | this.index++; 14 | }; 15 | 16 | this.getUpto = function(char) { 17 | if (!this.hasMore()) 18 | return undefined; 19 | var result = ''; 20 | while (this.hasMore() && (this.data[this.index] != char || this.data[this.index-1] == '\\')) { 21 | result += this.data[this.index++]; 22 | } 23 | return result; 24 | }; 25 | 26 | this.skip = function(count) { 27 | count = count || 1; 28 | var before = this.index; 29 | this.index = Math.min(this.index+count, this.data.length); 30 | }; 31 | 32 | this.rewind = function() { 33 | this.index = 0; 34 | }; 35 | 36 | this.lookingAt = function() { 37 | if (!this.hasMore()) 38 | return undefined; 39 | return this.data[this.index]; 40 | }; 41 | 42 | this.hasMore = function() { 43 | return this.remaining() > 0; 44 | }; 45 | 46 | this.remaining = function() { 47 | return this.data.length - this.index; 48 | }; 49 | 50 | } 51 | 52 | module.exports = Buffer; 53 | -------------------------------------------------------------------------------- /examples/broken-links.js: -------------------------------------------------------------------------------- 1 | /* 2 | This program reads one or more httpd log files (specified on the command line) and generates CSV output 3 | containing all requests with a referer and status 404, i.e. broken links. 4 | */ 5 | 6 | var fs = require('fs'); 7 | var Alpine = require('alpine'); 8 | var alpine = new Alpine(); 9 | 10 | var files = process.argv.splice(2); 11 | 12 | files.forEach(function(file){ 13 | alpine.parseReadStream(fs.createReadStream(file, {encoding: "utf8"}), 14 | function(data) { 15 | if (data.status == 404 && data["RequestHeader Referer"] != "-") { 16 | console.log(data["RequestHeader Referer"]+";"+data.request); 17 | } 18 | 19 | } 20 | ); 21 | }) 22 | 23 | 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "alpine", 3 | "version": "0.2.1", 4 | "description": "Alpine - the Apache Log Parser", 5 | "main": "alpine.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "dependencies": { 10 | "byline": "^4.1.1", 11 | "through2": "^0.6.2", 12 | "underscore.string": "^3.3.5" 13 | }, 14 | "devDependencies": { 15 | "mocha": "^8.3.1" 16 | }, 17 | "scripts": { 18 | "test": "mocha" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "https://github.com/blarsen/node-alpine.git" 23 | }, 24 | "keywords": [ 25 | "apache", 26 | "mod_log", 27 | "log", 28 | "parser", 29 | "combined", 30 | "clf" 31 | ], 32 | "author": "Bjørn Hell Larsen ", 33 | "license": "Apache License 2.", 34 | "bugs": { 35 | "url": "https://github.com/blarsen/node-alpine/issues" 36 | }, 37 | "homepage": "https://github.com/blarsen/node-alpine" 38 | } 39 | -------------------------------------------------------------------------------- /stringreader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by blarsen on 03.10.14. 3 | */ 4 | 5 | var util = require('util'); 6 | var Readable = require('stream').Readable; 7 | var Buffer = require('buffer').Buffer; 8 | 9 | function StringReader(str) { 10 | Readable.call(this); 11 | this.data = str; 12 | } 13 | 14 | util.inherits(StringReader, Readable); 15 | 16 | module.exports = StringReader; 17 | 18 | StringReader.prototype._read =function(n) { 19 | this.push(this.data); 20 | this.push(null); 21 | } -------------------------------------------------------------------------------- /test/test_alpine.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by blarsen on 03.10.14. 3 | */ 4 | 5 | 6 | var Alpine = require("../alpine"); 7 | var assert = require('assert'); 8 | var fs = require('fs'); 9 | var byline = require('byline'); 10 | var StringReader = require('../stringreader'); 11 | 12 | describe('Alpine', function () { 13 | 14 | it("should have predefined log formats", function () { 15 | assert(Alpine.LOGFORMATS.COMBINED, "No combined log format"); 16 | assert(Alpine.LOGFORMATS.CLF, "No common log format"); 17 | assert(Alpine.LOGFORMATS.CLF_VHOST, "No common log format with vhosts"); 18 | }) 19 | 20 | it("should let me specify a log format", function () { 21 | var a = new Alpine("%h"); 22 | assert(a.getLogFormat() == "%h"); 23 | }) 24 | 25 | it("should default to COMBINED log format", function () { 26 | var a = new Alpine(); 27 | assert(a.getLogFormat() == Alpine.LOGFORMATS.COMBINED); 28 | }) 29 | 30 | it("should allow me to set the log format programatically", function () { 31 | var a = new Alpine(); 32 | a.setLogFormat("%h %r"); 33 | assert(a.getLogFormat() === "%h %r"); 34 | }) 35 | 36 | it("should throw an error when it receives an unknown log format field", function () { 37 | var a = new Alpine(); 38 | try { 39 | a.setLogFormat("No computer stands in my way"); 40 | } catch (err) { 41 | return; 42 | } 43 | throw new Error("Did not get expected exception"); 44 | }) 45 | 46 | it("should handle single-field formats correctly", function () { 47 | var a = new Alpine("%h"); 48 | var result = a.parseLine("foo.bar.baz"); 49 | assert(result.remoteHost == "foo.bar.baz"); 50 | }) 51 | 52 | it("should handle two-field formats correctly", function () { 53 | var a = new Alpine("%h %a"); 54 | var result = a.parseLine("foo.bar.baz 129.240.2.40"); 55 | assert(result.remoteHost == "foo.bar.baz"); 56 | assert(result.remoteIP == "129.240.2.40"); 57 | }) 58 | 59 | it("should handle quoted fields correctly", function () { 60 | var a = new Alpine("\"%h\""); 61 | var result = a.parseLine("\"foo.bar.baz\""); 62 | assert(result.remoteHost == "foo.bar.baz", "Wrong remotehost: " + result.remoteHost); 63 | }) 64 | 65 | it("should handle quoted quotes in the request field correctly", function() { 66 | var a = new Alpine("\"%r\""); 67 | var result = a.parseLine('"a\\"quoted quote"'); 68 | assert(result.request == 'a\\"quoted quote'); 69 | }) 70 | 71 | it("should handle the time field correctly", function () { 72 | var a = new Alpine("%t"); 73 | var result = a.parseLine("[01/Oct/2014:04:05:11 +0200]"); 74 | assert(result.time == "01/Oct/2014:04:05:11 +0200", "Wrong time: " + result.time); 75 | }) 76 | 77 | it("should handle a combined log format line correctly", function () { 78 | var a = new Alpine(); 79 | var result = a.parseLine( 80 | '109.247.114.201 - - [01/Oct/2014:12:19:00 +0200] "GET /altibox/js/commons/jquery.xml2json.js?_=1412158740334 HTTP/1.1" 200 2701 "https://www.sfjbb.no/" "Mozilla/5.0 (iPhone; CPU iPhone OS 8_0 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Version/8.0 Mobile/12A365 Safari/600.1.4"' 81 | ); 82 | 83 | assert(result.remoteHost === "109.247.114.201"); 84 | assert(result['RequestHeader User-agent'] === "Mozilla/5.0 (iPhone; CPU iPhone OS 8_0 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Version/8.0 Mobile/12A365 Safari/600.1.4"); 85 | assert(result['RequestHeader Referer'] === "https://www.sfjbb.no/"); 86 | assert(result.sizeCLF === "2701", "Wrong size: " + result.sizeCLF); 87 | }) 88 | 89 | it("should let me serve it a readstream and do per-line callbacks", function(done) { 90 | var a = new Alpine("%h"); 91 | var sr = new StringReader("foo.bar.baz\nwww.uio.no\nwww.altibox.no"); 92 | var lines = 0; 93 | a.parseReadStream(sr, function(data) { 94 | lines++; 95 | if (lines == 1) 96 | assert(data.remoteHost === "foo.bar.baz") 97 | else if (lines == 2) 98 | assert(data.remoteHost === "www.uio.no") 99 | else if (lines == 3) 100 | assert(data.remoteHost === "www.altibox.no") 101 | 102 | if (lines == 3) { 103 | done(); 104 | } 105 | }) 106 | }) 107 | 108 | /* Normally disabled since it does I/O 109 | it("should do magic", function() { 110 | 111 | byline.createStream(fs.createReadStream('access_log.1', {encoding: "utf8"})) 112 | .pipe(new Alpine().getStringStream()) 113 | .pipe(fs.createWriteStream("a.out")); 114 | 115 | }) 116 | */ 117 | 118 | }) 119 | -------------------------------------------------------------------------------- /test/test_buffer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by blarsen on 03.10.14. 3 | */ 4 | 5 | var Buffer = require("../buffer"); 6 | var assert = require('assert'); 7 | 8 | describe('Buffer', function () { 9 | var buf; 10 | 11 | before(function () { 12 | buf = new Buffer("a b", 0); 13 | }) 14 | 15 | it("should handle basic functions ok", function () { 16 | var xbuf = new Buffer("a b c", 0); 17 | assert(xbuf.remaining() == 5); 18 | assert(xbuf.hasMore()); 19 | assert(xbuf.getUpto(" ") === "a"); 20 | assert(xbuf.remaining() == 4); 21 | xbuf.skipSpaces(); 22 | assert(xbuf.remaining() == 3); 23 | assert(xbuf.getUpto("x") === "b c"); 24 | assert(!xbuf.hasMore()); 25 | assert(xbuf.remaining() == 0); 26 | assert(xbuf.getUpto('x') === undefined); 27 | }) 28 | 29 | it("should be good at looking, skipping and rewinding", function () { 30 | var xbuf = new Buffer("abcdef", 0); 31 | assert(xbuf.lookingAt() === "a", "It doesn't know what it's looking at"); 32 | xbuf.skip(1); 33 | assert(xbuf.lookingAt() === "b"); 34 | xbuf.skip(2); 35 | assert(xbuf.lookingAt() === "d"); 36 | xbuf.skip(12); 37 | assert(!xbuf.hasMore()); 38 | xbuf.rewind(); 39 | assert(xbuf.lookingAt() == "a"); 40 | 41 | 42 | }) 43 | 44 | }) 45 | --------------------------------------------------------------------------------