UNPKG

7.14 kBtext/coffeescriptView Raw
1# coffee = require 'coffee-script'
2moment = require 'moment'
3colors = require './colors'
4path = require 'path'
5levels = require './levels'
6
7cwd = process.cwd()
8reg = [
9 /\b(file|lineno|stack|stackColored)\b/
10 /\b(now|time|date|fulltime|numbertime|mstimestamp|timestamp|moment)\b/
11 /\(([^\)\(]+?):(\d+):\d+\)$/]
12stackNames = ['file', 'lineno', 'stack', 'stackColored']
13timeNames = ['now', 'time', 'date', 'fulltime', 'numbertime', 'mstimestamp', 'timestamp']
14
15timeFormats =
16 time : 'HH:mm:ss'
17 date : 'YYYY-MM-DD'
18 fulltime : 'YYYY-MM-DD HH:mm:ss'
19 numbertime : 'YYYYMMDDHHmmss'
20
21justlogPath = __dirname + '/log' + path.extname __filename
22
23anonymous = '<anonymous>'
24
25module.exports = pattern =
26 ###
27 /**
28 * pre-defined log patterns
29 * @type {object}
30 * colored :
31 * - simple-color: log message and colored level text
32 * - simple-nocolor: like simple without color
33 * - color: tracestack, time, log message and colored level text
34 * nocolor :
35 * - nocolor: like color without color
36 * - event-color: time, log message and colored event
37 * nocolor :
38 * - event-nocolor: like event-color without color
39 * - file : fulltime, tracestack, log message and level text
40 * connect-middleware : ()
41 * - accesslog: apache access-log
42 * - accesslog-rt: like access-log with response-time on the end (with microsecond)
43 * - accesslog-color: like ACCESSLOG-RT with ansi colored
44 ###
45 pre :
46 'simple-nocolor' : '{level} {msg}'
47 'simple-color' : '{color.level level} {msg}'
48 'nocolor' : '{time} [{levelTrim}] ({stack}) {msg}'
49 'color' : '{time} {color.level level} {stackColored} {msg}'
50 'file' : '{fulltime} [{levelTrim}] ({stack}) {msg}'
51 'event-color' : '{time} {color.event event} {args}'
52 'event-nocolor' : '{fulltime} {event} {args}'
53 'accesslog' : '''
54 {remote-address} {ident} {user}
55 [{now "DD/MMM/YYYY:HH:mm:ss ZZ"}]
56 "{method} {url} HTTP/{version}"
57 {status} {content-length}
58 "{headers.referer}" "{headers.user-agent}"
59 '''.replace /\n/g, ' '
60 'accesslog-rt' : '''
61 {remote-address} {ident} {user}
62 [{now 'DD/MMM/YYYY:HH:mm:ss ZZ'}]
63 "{method} {url} HTTP/{version}"
64 {status} {content-length}
65 "{headers.referer}" "{headers.user-agent}" {rt}
66 '''.replace /\n/g, ' '
67 'accesslog-color' : '''
68 {remote-address@yellow} {ident} {user}
69 [{now 'DD/MMM/YYYY:HH:mm:ss ZZ'}]
70 "{color.method method} {url@underline,bold,blue} HTTP/{version}"
71 {color.status status} {content-length}
72 "{headers.referer@blue}" "{headers.user-agent@cyan}" {rt}
73 '''.replace /\n/g, ' '
74 'accesslog-traceid' : '''
75 {remote-address}:{remote-port} {ident} {user}
76 [{now 'DD/MMM/YYYY:HH:mm:ss ZZ'}]
77 "{method} {url} HTTP/{version}"
78 {status} {content-length}
79 "{headers.referer}" "{headers.user-agent}" {rt} {traceid}
80 '''.replace /\n/g, ' '
81
82 ###
83 /**
84 * compile log-format pattern to a render function
85 * @param {string} code pattern string
86 * @param {Object} options on compile; placeholder: placeholder for empty value
87 * @return {function} pattern render function
88 * - {bool} [trace] need tracestack info
89 * - {bool} [time] need logtime info
90 * - {string} [pattern] pattern text
91 ###
92 compile : (pat, options = placeholder : '-')->
93 {placeholder, traceid} = options
94 code = pattern.pre[pat] ? pat # check perdefines
95 code = code.replace /"/g, '\\"' # slash '"'
96 useStack = false
97 useTime = false
98 # match all tokens
99 funcs = []
100 code = code.replace ///
101 \{
102 ([a-zA-Z][\-\w]+) # var name
103 (?:\.([\w\-]+))? # sub key name
104 (?:\s([^}@]+?))? # function args
105 (?:@((?:[a-z_]+,?)+))? # style
106 \}
107 ///g, (match, name, key, args, style) ->
108 useStack = true if name in stackNames # need tracestack
109 useTime = true if name in timeNames # need time
110 codes = []
111
112 # push style block
113 code = ''
114 styles = style.split ',' if style
115 if styles
116 code += colors[style] for style in styles
117 codes.push '"' + code + '"'
118
119 # push vars block
120 code = ''
121 if args # is function
122 num = funcs.length
123 funcs.push [name, key, args.replace(/\\"/g, '"')]
124 code = "__func[#{num}]"
125 else if name of timeFormats# is time vars
126 code += "__vars.now('#{timeFormats[name]}')"
127 else # is vars
128 code += "__vars['#{name}']#{if key then "['#{key}']" else ''}"
129 codes.push "(#{code}||\"#{placeholder}\")"
130
131 # push style reset block
132 codes.push '"' + colors.reset + '"' if styles
133 '"+\n' + codes.join('+\n') + '+\n"'
134
135 # remove empty string
136 code = ('"' + code + '"').replace(/^""\+$/mg, '')
137 code = "return #{code.trim()};"
138 # __func prefix
139 funcCode = []
140 if funcs.length > 0
141 funcCode.push 'var __func = [];'
142 for [name, key, args] in funcs
143 args = "__vars['#{args}']" if args[0].match /[a-z]/i
144 funcCode.push "__func.push(__vars['#{name}']#{if key then "['#{key}']" else ''}(#{args}));"
145 # funcCode.push '}'
146 code = funcCode.join(";\n")+code
147
148 # make function
149 func = new Function('__vars', code)
150 func.stack = useStack # need trace stack for get log position
151 func.time = useTime # need moment for time format
152 func.pattern = pat
153 func
154
155 ###
156 /**
157 * render one line
158 * @param {function} render attern render function (generate by .compile())
159 * @param {string]} msg log messages
160 * @param {string} level log level
161 * @return {string} log line text
162 ###
163 format : (render, msg, level) ->
164 msg = '' if msg is null
165 msg = msg: msg.toString() if typeof msg isnt 'object'
166 msg.color = colors
167 msg.level = levels.text[level]
168 msg.levelTrim = msg.level.trim()
169 if render.time
170 msg.now = getFormatedTime
171 msg.mstimestamp = moment().valueOf()
172 msg.timestamp = Math.floor msg.mstimestamp / 1000
173 msg = trackStack msg if render.stack
174 render(msg) + "\n"
175
176FORMATED_TIME = {}
177
178getFormatedTime = ( format ) ->
179 unless format of FORMATED_TIME
180 #TODO different interval time with difference format
181 interval = 1000
182 timer = ->
183 now = moment()
184 setTimeout(timer, interval-now.milliseconds())
185 FORMATED_TIME[format] = now.format format
186 timer()
187 FORMATED_TIME[format]
188
189trackStack = (msg) ->
190 try
191 throw new Error
192 catch err
193 return stackProcess err, msg
194
195stackProcess = (err, msg) ->
196 stacks = err.stack.split "\n"
197 flag = false
198 for stack in stacks
199 if res = stack.match reg[2]
200 if res[1] isnt justlogPath and res[1] isnt __filename and res[1] isnt anonymous
201 flag = true
202 break
203 if flag is false
204 msg.file = 'NULL'
205 msg.lineno = 0
206 else
207 file = res[1]
208 msg.file = if file[0] is '/' then path.relative cwd, file else file
209 msg.lineno = res[2]
210 msg.stack = "#{msg.file}:#{msg.lineno}"
211 msg.stackColored = "#{colors.underline}#{colors.cyan}#{msg.file}:#{colors.yellow}#{msg.lineno}#{colors.reset}"
212 msg