1# -*- coding: utf-8 -*-
2from datetime import datetime
3from datetime import timedelta
4from haproxy import DELTA_REGEX
5from haproxy import START_REGEX
6
7
8def filter_ip(ip):
9    """Filter :class:`.Line` objects by IP.
10
11    :param ip: IP that you want to filter to.
12    :type ip: string
13    :returns: a function that filters by the provided IP.
14    :rtype: function
15    """
16    def filter_func(log_line):
17        return log_line.get_ip() == ip
18
19    return filter_func
20
21
22def filter_ip_range(ip_range):
23    """Filter :class:`.Line` objects by IP range.
24
25    Both *192.168.1.203* and *192.168.1.10* are valid if the provided ip
26    range is ``192.168.1`` whereas *192.168.2.103* is not valid (note the
27    *.2.*).
28
29    :param ip_range: IP range that you want to filter to.
30    :type ip_range: string
31    :returns: a function that filters by the provided IP range.
32    :rtype: function
33    """
34    def filter_func(log_line):
35        ip = log_line.get_ip()
36        if ip:
37            return ip.startswith(ip_range)
38
39    return filter_func
40
41
42def filter_path(path):
43    """Filter :class:`.Line` objects by their request path.
44
45    :param path: part of a path that needs to be on the request path.
46    :type path: string
47    :returns: a function that filters by the provided path.
48    :rtype: function
49    """
50    def filter_func(log_line):
51        return path in log_line.http_request_path
52
53    return filter_func
54
55
56def filter_ssl(ignore=True):
57    """Filter :class:`.Line` objects that from SSL connections.
58
59    :param ignore: parameter to be ignored just to conform to the rule that all
60      filters need a parameter
61    :type ignore: bool
62    :returns: a function that filters SSL log lines.
63    :rtype: function
64    """
65    def filter_func(log_line):
66        return log_line.is_https()
67
68    return filter_func
69
70
71def filter_slow_requests(slowness):
72    """Filter :class:`.Line` objects by their response time.
73
74    :param slowness: minimum time, in milliseconds, a server needs to answer
75      a request. If the server takes more time than that the log line is
76      accepted.
77    :type slowness: string
78    :returns: a function that filters by the server response time.
79    :rtype: function
80    """
81    def filter_func(log_line):
82        slowness_int = int(slowness)
83        return slowness_int <= log_line.time_wait_response
84
85    return filter_func
86
87
88def filter_wait_on_queues(max_waiting):
89    """Filter :class:`.Line` objects by their queueing time in
90    HAProxy.
91
92    :param max_waiting: maximum time, in milliseconds, a request is waiting on
93      HAProxy prior to be delivered to a backend server. If HAProxy takes less
94      than that time the log line is counted.
95    :type max_waiting: string
96    :returns: a function that filters by HAProxy queueing time.
97    :rtype: function
98    """
99    def filter_func(log_line):
100        waiting = int(max_waiting)
101        return waiting >= log_line.time_wait_queues
102
103    return filter_func
104
105
106def filter_time_frame(start, delta):
107    """Filter :class:`.Line` objects by their connection time.
108
109    :param start: a time expression (see -s argument on --help for its format)
110      to filter log lines that are before this time.
111    :type start: string
112    :param delta: a relative time expression (see -s argument on --help for
113      its format) to limit the amount of time log lines will be considered.
114    :type delta: string
115    :returns: a function that filters by the time a request is made.
116    :rtype: function
117    """
118    start_value = start
119    delta_value = delta
120    end_value = None
121
122    if start_value is not '':
123        start_value = _date_str_to_datetime(start_value)
124
125    if delta_value is not '':
126        delta_value = _delta_str_to_timedelta(delta_value)
127
128    if start_value is not '' and delta_value is not '':
129        end_value = start_value + delta_value
130
131    def filter_func(log_line):
132        if start_value is '':
133            return True
134        elif start_value > log_line.accept_date:
135            return False
136
137        if end_value is None:
138            return True
139        elif end_value < log_line.accept_date:
140            return False
141
142        return True
143
144    return filter_func
145
146
147def filter_status_code(http_status):
148    """Filter :class:`.Line` objects by their HTTP status code.
149
150    :param http_status: HTTP status code (200, 404, 502...) to filter lines
151      with.
152    :type http_status: string
153    :returns: a function that filters by HTTP status code.
154    :rtype: function
155    """
156    def filter_func(log_line):
157        return log_line.status_code == http_status
158
159    return filter_func
160
161
162def filter_status_code_family(family_number):
163    """Filter :class:`.Line` objects by their family of HTTP status
164    code, i.e. 2xx, 3xx, 4xx
165
166    :param family_number: First digit of the HTTP status code family, i.e. 2
167      to get all the 2xx status codes, 4 for the client errors and so on.
168    :type family_number: string
169    :returns: a function that filters by HTTP status code family.
170    :rtype: function
171    """
172    def filter_func(log_line):
173        return log_line.status_code.startswith(family_number)
174
175    return filter_func
176
177
178def filter_http_method(http_method):
179    """Filter :class:`.Line` objects by their HTTP method used (i.e.
180    GET, POST...).
181
182    :param http_method: HTTP method (POST, GET...).
183    :type http_method: string
184    :returns: a function that filters by the given HTTP method.
185    :rtype: function
186    """
187    def filter_func(log_line):
188        return log_line.http_request_method == http_method
189
190    return filter_func
191
192
193def filter_backend(backend_name):
194    """Filter :class:`.Line` objects by the HAProxy backend name
195    they were processed with.
196
197    :param backend_name: Name of the HAProxy backend section to investigate.
198    :type backend_name: string
199    :returns: a function that filters by the given backend name.
200    :rtype: function
201    """
202    def filter_func(log_line):
203        return log_line.backend_name == backend_name
204
205    return filter_func
206
207
208def filter_frontend(frontend_name):
209    """Filter :class:`.Line` objects by the HAProxy frontend name
210    the connection arrived from.
211
212    :param frontend_name: Name of the HAProxy frontend section to investigate.
213    :type frontend_name: string
214    :returns: a function that filters by the given frontend name.
215    :rtype: function
216    """
217    def filter_func(log_line):
218        return log_line.frontend_name == frontend_name
219
220    return filter_func
221
222
223def filter_server(server_name):
224    """Filter :class:`.Line` objects by the downstream server that
225    handled the connection.
226
227    :param server_name: Name of the server HAProxy send the connection to.
228    :type server_name: string
229    :returns: a function that filters by the given server name.
230    :rtype: function
231    """
232    def filter_func(log_line):
233        return log_line.server_name == server_name
234
235    return filter_func
236
237
238def filter_response_size(size):
239    """Filter :class:`.Line` objects by the response size (in bytes).
240
241    Specially useful when looking for big file downloads.
242
243    :param size: Minimum amount of bytes a response body weighted.
244    :type size: string
245    :returns: a function that filters by the response size.
246    :rtype: function
247    """
248    if size.startswith('+'):
249        size_value = int(size[1:])
250    else:
251        size_value = int(size)
252
253    def filter_func(log_line):
254        bytes_read = log_line.bytes_read
255        if bytes_read.startswith('+'):
256            bytes_read = int(bytes_read[1:])
257        else:
258            bytes_read = int(bytes_read)
259
260        return bytes_read >= size_value
261
262    return filter_func
263
264
265def _date_str_to_datetime(date):
266    matches = START_REGEX.match(date)
267
268    raw_date_input = '{0}/{1}/{2}'.format(
269        matches.group('day'),
270        matches.group('month'),
271        matches.group('year')
272    )
273    date_format = '%d/%b/%Y'
274    if matches.group('hour'):
275        date_format += ':%H'
276        raw_date_input += ':{0}'.format(matches.group('hour'))
277    if matches.group('minute'):
278        date_format += ':%M'
279        raw_date_input += ':{0}'.format(matches.group('minute'))
280    if matches.group('second'):
281        date_format += ':%S'
282        raw_date_input += ':{0}'.format(matches.group('second'))
283
284    return datetime.strptime(raw_date_input, date_format)
285
286
287def _delta_str_to_timedelta(delta):
288    matches = DELTA_REGEX.match(delta)
289
290    value = int(matches.group('value'))
291    time_unit = matches.group('time_unit')
292
293    if time_unit == 's':
294        return timedelta(seconds=value)
295    elif time_unit == 'm':
296        return timedelta(minutes=value)
297    elif time_unit == 'h':
298        return timedelta(hours=value)
299    if time_unit == 'd':
300        return timedelta(days=value)
301