(defpackage :access-log-reader
(:shadowing-import-from :multi-fun :rest)
(:shadowing-import-from :data-lens :pick :defun-ct)
(:use :cl :multi-fun :data-lens :alexandria :fw.lu)
(:export ))
(in-package :access-log-reader)
(defun make-timestamp (record)
(let ((local-time:*default-timezone* local-time:+utc-zone+))
(destructuring-bind (day month year time) record
(local-time:with-decoded-timestamp (:hour hour :minute minute :sec second :nsec nsec) time
(local-time:encode-timestamp nsec second minute hour day month year)))))
(defun extract-query-params (s)
(serapeum:mapply 'cons
(map 'list
(serapeum:op (coerce (fwoar.string-utils:split #\= _ :count 2) 'list))
(fwoar.string-utils:split #\& s))))
(defparameter +access-log-parser+
(list (delimited-field #\:)
(delimited-field #\space)
(as (delimited-field #\space) (lambda (s) (subseq s 1 (1- (length s)))))
(as (delimited-field #\space) (lambda (s) (unless (equal s "-") (parse-integer s))))
(as (delimited-field #\space) (lambda (s) (unless (equal s "-") (parse-integer s))))
(as (delimited-field #\space) #'parse-integer)
(as (delimited-field #\space) (lambda (s) (unless (equal s "-") (parse-integer s))))
(as (subformat (v (ignore-char #\[)
(as (delimited-field #\/) #'parse-integer)
(as (delimited-field #\/) (serapeum:op (position _ local-time:+short-month-names+ :test 'equal)))
(as (delimited-field #\:) #'parse-integer)
(as (delimited-field #\]) (lambda (x) (local-time:parse-timestring (remove #\space x) :allow-missing-date-part t)))))
'make-timestamp)
(whitespace)
(as (delimited-field #\space) (lambda (s) (subseq s 1)))
(splat-result (as (delimited-field #\space)
(alexandria:compose
(data-lens:juxt 'quri:uri-path
(alexandria:compose 'extract-query-params 'quri:uri-query))
'quri:uri)))
(delimited-field #\")
(delimited-field #\space)
(delimited-field #\space)
(delimited-field #\space)
(ignore-char #\")
(as (delimited-field #\") 'quri:uri)
(whitespace)
(rest)))
(defun parse-log (f)
(parse-file +access-log-parser+ f))
|