UNPKG

6.16 kBJavaScriptView Raw
1/*
2Supports filtering call data for safe logging, for example to strip passwords
3or credit card numbers.
4
5Example usage: FAAS_IX_LOGGING_FILTER=./api/logging_filter.js ruby node_modules/faas/bin/faas-ix-server-ruby api/v0/
6
7Contents of api/logging_filter.js:
8----------------------------------
9
10exports.filter = function(callData){
11 if (callData.api === 'Api.V0.Auth.login') {
12 callData.args[1] = '[REDACTED]'
13 }
14 return callData
15}
16
17exports.getKeyFilterRegexes = function(oldRegexes){
18 oldRegexes.push(/name/)
19 return oldRegexes
20}
21
22*/
23
24
25var Util = require('util')
26 , Path = require('path')
27 , Base = require('../base')
28
29var debug = Base.logger('ix-logging_filter')
30
31
32var LOGGING_FILTER = null
33var FILTERED_KEYS = [ /password/i ]
34
35
36exports.loadFilter = function(callback){
37 var filterPath = Base.env.IX_LOGGING_FILTER;
38 if (filterPath) {
39 console.log('Loading logging filter '+filterPath)
40 try {
41 LOGGING_FILTER = require(filterPath)
42 }
43 catch (e) {
44 try {
45 LOGGING_FILTER = require(Path.join(process.cwd(), filterPath))
46 }
47 catch (e2) {
48 console.log("Unable to load logging filter from "+filterPath+": "+e2+ ' (working dir: '+process.cwd()+')')
49 }
50 }
51 }
52 if (LOGGING_FILTER && LOGGING_FILTER.getKeyFilterRegexes) {
53 try {
54 FILTERED_KEYS = LOGGING_FILTER.getKeyFilterRegexes(FILTERED_KEYS)
55 }
56 catch (e) {
57 console.log('Error setting logging filters: '+e)
58 }
59 }
60 callback()
61}
62
63exports.logCallRequest = function(callData){
64 try {
65 var filteredCall = filterCall(callData)
66 console.log(JSON.stringify(filteredCall))
67 }
68 catch(e){}
69}
70
71var filterCall = function(callData){
72 var filtered
73 try {
74 var startTime = (new Date()).getTime()
75 // Deep-dup so that we don't modify the actual args.
76 filtered = JSON.parse(JSON.stringify(callData))
77 // Don't include startTime in the logged data.
78 callData.ts = callData.ts || {}
79 callData.ts.apiStart = startTime
80 filteredArgs = filtered.args || []
81 filteredArgs = filterDataHelper(filteredArgs)
82 filtered.args = filteredArgs
83 if (LOGGING_FILTER && LOGGING_FILTER.filter) {
84 filtered = LOGGING_FILTER.filter(filtered)
85 }
86 }
87 catch(e){
88 console.log("Error in logging filter: "+e)
89 }
90 return filtered
91}
92
93exports.wrapResponder = function(callData, responder_err_data){
94 return function(err, data){
95 try {
96 callData.ts.apiEnd = (new Date()).getTime()
97 var duration = callData.ts.apiEnd - callData.ts.apiStart
98 console.log(JSON.stringify({
99 api: callData.api
100 , duration: duration
101 , responseType: err ? 'ERROR' : 'SUCCESS'
102 , returnType: typeOf(err || data)
103 , uuid: callData.req.uuid
104 }))
105 }
106 catch(e){}
107 responder_err_data(err, data)
108 }
109}
110
111// http://stackoverflow.com/questions/472418/why-is-4-not-an-instance-of-number#472427
112var typeOf = function(value){
113 var type = typeof value;
114 switch(type) {
115 case 'object':
116 return value === null ? 'null' : Object.prototype.toString.call(value).match(/^\[object (.*)\]$/)[1]
117 case 'function':
118 return 'Function';
119 default:
120 return type;
121 }
122}
123
124var filterDataHelper = function(arg, prefix){
125 var i, key
126 var newPrefix, matched
127 if (Util.isArray(arg)) {
128 for (i = 0; i < arg.length; i++) {
129 arg[i] = filterDataHelper(arg[i])
130 }
131 }
132 else if (typeof arg === 'object') {
133 for (key in arg) { if (arg.hasOwnProperty(key)) {
134 newPrefix = prefix || ''
135 if (newPrefix) { newPrefix += '.' }
136 newPrefix = newPrefix + key
137 matched = matchedFilter(newPrefix)
138 if (matched) {
139 arg[key] = '[FILTERED:'+matched+']'
140 }
141 else {
142 arg[key] = filterDataHelper(arg[key], newPrefix)
143 }
144 } }
145 }
146 // else arg doesn't change.
147 return arg
148}
149
150var matchedFilter = function(key){
151 var i, re
152 for (i = 0; i < FILTERED_KEYS.length; i++) {
153 re = FILTERED_KEYS[i]
154 if (key.match(re)) {
155 return re
156 }
157 }
158 return null
159}
160
161
162exports.logHttpRequest = function(faasReq){
163 try {
164 var startTime = (new Date()).getTime()
165 faasReq.ts = faasReq.ts || {}
166 faasReq.ts.requestStart = startTime
167 console.log(JSON.stringify({
168 uuid: faasReq.headers['x-faas-uuid'],
169 ip: faasReq.headers['x-forwarded-for'].split(/\s*,\s*/)[0],
170 type: 'request',
171 method: faasReq.method,
172 url: faasReq.origProto+'://'+faasReq.origHost+faasReq.url,
173 contentLength: faasReq.headers['content-length'],
174 userAgent: faasReq.headers['user-agent'],
175 referer: faasReq.headers['referer'],
176 }))
177 }
178 catch(e){}
179}
180
181exports.logHttpResponseStart = function(faasReq, response){
182 try {
183 faasReq.ts.responseStart = (new Date()).getTime()
184 var timeDiff = faasReq.ts.responseStart - faasReq.ts.requestStart
185 console.log(JSON.stringify({
186 uuid: faasReq.headers['x-faas-uuid'],
187 ip: faasReq.headers['x-forwarded-for'].split(/\s*,\s*/)[0],
188 type: 'responseStart',
189 method: faasReq.method,
190 url: faasReq.origProto+'://'+faasReq.origHost+faasReq.url,
191 contentType: response.headers && response.headers['content-type'],
192 transferEncoding: response.headers && response.headers['transfer-encoding'],
193 contentLength: response.headers && response.headers['content-length'],
194 statusCode: response.statusCode,
195 statusMessage: response.statusMessage,
196 lag: timeDiff
197 }))
198 }
199 catch(e){}
200}
201
202exports.logHttpResponseEnd = function(faasReq){
203 try {
204 faasReq.ts.responseEnd = (new Date()).getTime()
205 var timeDiff = faasReq.ts.responseEnd - faasReq.ts.requestStart
206 console.log(JSON.stringify({
207 uuid: faasReq.headers['x-faas-uuid'],
208 type: 'responseEnd',
209 size: faasReq.responseBytes,
210 duration: timeDiff
211 }))
212 }
213 catch(e){}
214}
215
216exports.logHttpResponseError = function(faasReq){
217 try {
218 faasReq.ts.responseError = (new Date()).getTime()
219 var timeDiff = faasReq.ts.responseError - faasReq.ts.requestStart
220 console.log(JSON.stringify({
221 uuid: faasReq.headers['x-faas-uuid'],
222 type: 'responseError',
223 size: faasReq.responseBytes,
224 duration: timeDiff
225 }))
226 }
227 catch(e){}
228}