Skip to content

Commit d1cd2fe

Browse files
Merge pull request #367 from simon04/caddy_json
Add Caddy json log support
2 parents 5624527 + e6e4d00 commit d1cd2fe

File tree

3 files changed

+105
-0
lines changed

3 files changed

+105
-0
lines changed

import_logs.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,54 @@ def remove_ignored_groups(self, groups):
278278
for group in groups:
279279
del self.json[group]
280280

281+
class CaddyJsonFormat(BaseFormat):
282+
def __init__(self, name):
283+
super(CaddyJsonFormat, self).__init__(name)
284+
self.json = None
285+
self.date_format = '%Y-%m-%dT%H:%M:%S.%f'
286+
287+
def check_format_line(self, line):
288+
try:
289+
self.json = json.loads(line)
290+
return "request" in self.json and "user_id" in self.json and "resp_headers" in self.json
291+
except:
292+
return False
293+
294+
def match(self, line):
295+
try:
296+
self.json = json.loads(line)
297+
return self
298+
except:
299+
self.json = None
300+
return None
301+
302+
def get(self, key):
303+
try:
304+
return self.get_all().get(key)
305+
except KeyError:
306+
raise BaseFormatException()
307+
308+
def get_all(self,):
309+
tz = datetime.timezone.utc
310+
date = datetime.datetime.fromtimestamp(self.json['ts'], tz=tz)
311+
self.json['date'] = date.strftime(self.date_format)
312+
self.json['timezone'] = date.strftime('%z')
313+
self.json['length'] = str(self.json['size'])
314+
self.json['status'] = str(self.json['status'])
315+
self.json['generation_time_milli'] = str(self.json['duration'] * 1000.)
316+
self.json['userid'] = self.json['user_id']
317+
self.json['ip'] = self.json['request']['client_ip']
318+
self.json['host'] = self.json['request']['host']
319+
self.json['method'] = self.json['request']['method']
320+
self.json['path'] = self.json['request']['uri']
321+
self.json['referrer'] = next(iter(self.json['request']['headers'].get('Referer', [])), None)
322+
self.json['user_agent'] = next(iter(self.json['request']['headers'].get('User-Agent', [])), None)
323+
return self.json
324+
325+
def remove_ignored_groups(self, groups):
326+
for group in groups:
327+
del self.json[group]
328+
281329
class RegexFormat(BaseFormat):
282330

283331
def __init__(self, name, regex, date_format=None):
@@ -590,6 +638,7 @@ def get(self, key):
590638
'elb': RegexFormat('elb', _ELB_LOG_FORMAT, '%Y-%m-%dT%H:%M:%S'),
591639
'traefik_json': TraefikJsonFormat('traefik_json'),
592640
'nginx_json': NginxJsonFormat('nginx_json'),
641+
'caddy_json': CaddyJsonFormat('caddy_json'),
593642
'ovh': RegexFormat('ovh', _OVH_FORMAT),
594643
'haproxy': RegexFormat('haproxy', _HAPROXY_FORMAT, '%d/%b/%Y:%H:%M:%S.%f'),
595644
'gandi': RegexFormat('gandi', _GANDI_SIMPLE_HOSTING_FORMAT, '%d/%b/%Y:%H:%M:%S')

tests/logs/caddy_json.log

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{"level":"info","ts":1703373474.8155608,"logger":"http.log.access","msg":"handled request","request":{"remote_ip":"1.2.3.4","remote_port":"64985","client_ip":"1.2.3.4","proto":"HTTP/2.0","method":"GET","host":"example.com","uri":"/beta/","headers":{"Te":["trailers"],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:121.0) Gecko/20100101 Firefox/121.0"],"Accept":["text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/jxl,image/webp,*/*;q=0.8"],"Upgrade-Insecure-Requests":["1"],"Sec-Fetch-Mode":["navigate"],"Sec-Fetch-Dest":["document"],"Sec-Fetch-Site":["none"],"Sec-Fetch-User":["?1"],"Accept-Language":["en-IE"],"Accept-Encoding":["gzip, deflate, br"],"Dnt":["1"],"Sec-Gpc":["1"]},"tls":{"resumed":false,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"example.com"}},"bytes_read":0,"user_id":"","duration":0.001335486,"size":3609,"status":200,"resp_headers":{"Content-Encoding":["gzip"],"Vary":["Accept-Encoding"],"Server":["Caddy","nginx/1.20.1"],"Alt-Svc":["h3=\":443\"; ma=2592000"],"Date":["Sat, 23 Dec 2023 23:17:54 GMT"],"Content-Type":["text/html"],"Access-Control-Allow-Methods":["GET"],"Access-Control-Allow-Origin":["*"]}}
2+
{"level":"info","ts":1703373474.9011197,"logger":"http.log.access","msg":"handled request","request":{"remote_ip":"1.2.3.4","remote_port":"64985","client_ip":"1.2.3.4","proto":"HTTP/2.0","method":"GET","host":"example.com","uri":"/beta/assets/index-mMaLXldj.css","headers":{"Accept-Encoding":["gzip, deflate, br"],"Dnt":["1"],"Sec-Fetch-Dest":["style"],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:121.0) Gecko/20100101 Firefox/121.0"],"Accept":["text/css,*/*;q=0.1"],"Referer":["https://example.com/beta/"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-origin"],"Te":["trailers"],"Accept-Language":["en-IE"],"Sec-Gpc":["1"]},"tls":{"resumed":false,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"example.com"}},"bytes_read":0,"user_id":"","duration":0.00143684,"size":34534,"status":200,"resp_headers":{"Content-Length":["34534"],"Last-Modified":["Sat, 23 Dec 2023 12:27:10 GMT"],"Etag":["\"6586d21e-86e6\""],"Vary":["Accept-Encoding"],"Content-Encoding":["br"],"Accept-Ranges":["bytes"],"Date":["Sat, 23 Dec 2023 23:17:54 GMT"],"Content-Type":["text/css"],"Server":["Caddy","nginx/1.20.1"],"Alt-Svc":["h3=\":443\"; ma=2592000"]}}
3+
{"level":"info","ts":1703373475.141868,"logger":"http.log.access","msg":"handled request","request":{"remote_ip":"1.2.3.4","remote_port":"64985","client_ip":"1.2.3.4","proto":"HTTP/2.0","method":"GET","host":"example.com","uri":"/beta/assets/index-HIcRLzdf.js","headers":{"Accept":["*/*"],"Accept-Language":["en-IE"],"Accept-Encoding":["gzip, deflate, br"],"Sec-Fetch-Dest":["script"],"Sec-Fetch-Site":["same-origin"],"Te":["trailers"],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:121.0) Gecko/20100101 Firefox/121.0"],"Sec-Gpc":["1"],"Referer":["https://example.com/beta/"],"Sec-Fetch-Mode":["cors"],"Dnt":["1"]},"tls":{"resumed":false,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"example.com"}},"bytes_read":0,"user_id":"","duration":0.242570094,"size":217912,"status":200,"resp_headers":{"Server":["Caddy","nginx/1.20.1"],"Alt-Svc":["h3=\":443\"; ma=2592000"],"Content-Type":["application/javascript"],"Accept-Ranges":["bytes"],"Content-Length":["217912"],"Etag":["\"6586d21e-35338\""],"Content-Encoding":["br"],"Vary":["Accept-Encoding"],"Date":["Sat, 23 Dec 2023 23:17:54 GMT"],"Last-Modified":["Sat, 23 Dec 2023 12:27:10 GMT"]}}

tests/test_main.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ def _test_junk(format_name, log_file = None):
8585
assert(format.name == format_name)
8686

8787
def _test_multiple_spaces(format_name, log_file = None):
88+
if format_name == 'caddy_json':
89+
return
8890
if log_file is None:
8991
log_file = 'logs/%s.log' % format_name
9092

@@ -416,6 +418,22 @@ def check_traefik_json_groups(groups):
416418
assert groups['userid'] == '-'
417419
assert groups['user_agent'] == 'Prometheus/2.40.5'
418420

421+
def check_caddy_json_groups(groups):
422+
assert groups['ts'] == 1703373474.8155608
423+
assert groups['duration'] == 0.001335486
424+
assert groups['date'] == '2023-12-23T23:17:54.815561'
425+
assert groups['timezone'] == '+0000'
426+
assert groups['generation_time_milli'] == '1.3354860000000002'
427+
assert groups['host'] == 'example.com'
428+
assert groups['ip'] == '1.2.3.4'
429+
assert groups['length'] == '3609'
430+
assert groups['method'] == 'GET'
431+
assert groups['path'] == '/beta/'
432+
assert groups['referrer'] == None
433+
assert groups['status'] == '200'
434+
assert groups['userid'] == ''
435+
assert groups['user_agent'] == 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:121.0) Gecko/20100101 Firefox/121.0'
436+
419437
def check_icecast2_groups(groups):
420438
check_ncsa_extended_groups(groups)
421439

@@ -466,6 +484,41 @@ def _test_with_junk(format_name, path):
466484
# 'Testing parsing of format "common" with ncsa_extended log'
467485
_test( 'common', 'logs/ncsa_extended.log')
468486

487+
def test_caddy_json_parsing():
488+
"""test parsing of caddy_json.log file"""
489+
490+
file_ = 'logs/caddy_json.log'
491+
492+
import_logs.stats = import_logs.Statistics()
493+
import_logs.config = Config()
494+
import_logs.config.options.enable_static = False
495+
import_logs.config.options.replay_tracking = False
496+
import_logs.config.format = None
497+
import_logs.resolver = Resolver()
498+
import_logs.parser = import_logs.Parser()
499+
import_logs.Recorder = Recorder()
500+
Recorder.recorders = []
501+
import_logs.parser.parse(file_)
502+
503+
hits = [hit.__dict__ for hit in Recorder.recorders]
504+
505+
assert hits[0]['status'] == '200'
506+
assert hits[0]['is_error'] == False
507+
assert hits[0]['extension'] == '/beta/'
508+
assert hits[0]['is_download'] == False
509+
assert hits[0]['referrer'] == ''
510+
assert hits[0]['generation_time_milli'] == 1.3354860000000002
511+
assert hits[0]['host'] == 'foo'
512+
assert hits[0]['filename'] == 'logs/caddy_json.log'
513+
assert hits[0]['is_redirect'] == False
514+
assert hits[0]['date'] == datetime.datetime(2023, 12, 23, 23, 17, 54, 815561)
515+
assert hits[0]['lineno'] == 0
516+
assert hits[0]['ip'] == '1.2.3.4'
517+
assert hits[0]['path'] == '/beta/'
518+
assert hits[0]['is_robot'] == False
519+
assert hits[0]['full_path'] == '/beta/'
520+
assert hits[0]['user_agent'] == 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:121.0) Gecko/20100101 Firefox/121.0'
521+
469522
def test_iis_custom_format():
470523
"""test IIS custom format name parsing."""
471524

0 commit comments

Comments
 (0)