Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 561492c739 | |||
| 2f2e702cf8 | |||
| e4962c07a3 | |||
| 068daed762 | |||
| 3d7a46b641 | |||
| afdcf6eb0e | |||
| f5f05693e6 | |||
| 6eb2364867 | |||
| 5a038371bb | |||
| 8b6b633ef7 | |||
| 96159c05db | |||
| fac61d7113 |
@@ -0,0 +1,41 @@
|
||||
stages:
|
||||
- "build"
|
||||
- "upload"
|
||||
- "release"
|
||||
variables:
|
||||
RELEASE_DIRECTORY: "access-log-${CI_COMMIT_TAG}-linux-amd64"
|
||||
RELEASE_TARBALL: "${RELEASE_DIRECTORY}.tar.gz"
|
||||
PACKAGE_REGISTRY_URL: "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/access_log/${CI_COMMIT_TAG}/${RELEASE_TARBALL}"
|
||||
build:
|
||||
stage: "build"
|
||||
image: "crystallang/crystal:latest-alpine"
|
||||
rules:
|
||||
- if: "$CI_COMMIT_TAG"
|
||||
cache:
|
||||
paths:
|
||||
- "lib/"
|
||||
script:
|
||||
- "apk add sqlite-static"
|
||||
- "shards install"
|
||||
- "crystal build --release --static src/access_log.cr"
|
||||
- "strip --strip-all access_log"
|
||||
- "mkdir -p ${RELEASE_DIRECTORY}"
|
||||
- "mv access_log LICENSE ${RELEASE_DIRECTORY}"
|
||||
- "tar -czf ${RELEASE_TARBALL} ${RELEASE_DIRECTORY}"
|
||||
artifacts:
|
||||
paths:
|
||||
- "${RELEASE_TARBALL}"
|
||||
upload:
|
||||
stage: "upload"
|
||||
image: "curlimages/curl:latest"
|
||||
rules:
|
||||
- if: "$CI_COMMIT_TAG"
|
||||
script:
|
||||
- "curl --header \"JOB-TOKEN: ${CI_JOB_TOKEN}\" --upload-file ${RELEASE_TARBALL} ${PACKAGE_REGISTRY_URL}"
|
||||
release:
|
||||
stage: "release"
|
||||
image: "registry.gitlab.com/gitlab-org/release-cli:latest"
|
||||
rules:
|
||||
- if: "$CI_COMMIT_TAG"
|
||||
script:
|
||||
- "release-cli create --name \"Release ${CI_COMMIT_TAG}\" --tag-name ${CI_COMMIT_TAG} --assets-link \"{\\\"name\\\":\\\"${RELEASE_TARBALL}\\\",\\\"url\\\":\\\"${PACKAGE_REGISTRY_URL}\\\",\\\"link_type\\\":\\\"package\\\"}\""
|
||||
@@ -1,6 +1,6 @@
|
||||
# access_log
|
||||
|
||||
Receives access logs from stdin in JSON format and stores them on
|
||||
Receives access logs on a UNIX socket in JSON format and stores them on
|
||||
a database. It **intentionally** doesn't collect IP addresses. It
|
||||
doesn't respect the Do Not Track (DNT) header though, because we're not
|
||||
collecting personally identifiable data. Referrer collection is
|
||||
@@ -111,8 +111,10 @@ Configure Nginx to format access log as JSON. You can configure
|
||||
`http_referer` (double and single "r" respectively, the second is a typo
|
||||
on the HTTP specification).
|
||||
|
||||
Install `daemonize` and run `access_logd` to create `access.log` as
|
||||
a FIFO node, so Nginx writes to it and `access_log` can read from it.
|
||||
Install `daemonize` and run `access_logd`. By default it creates a UNIX
|
||||
socket on `/tmp/access_log.socket` so Nginx writes can write to it using
|
||||
its [syslog support](https://nginx.org/en/docs/syslog.html).
|
||||
|
||||
Check `/var/log/nginx/error.log` for debugging.
|
||||
|
||||
`ACCESS_LOG_FLAGS` is the env variable to pass flags to `access_logd`.
|
||||
@@ -122,7 +124,7 @@ container](https://0xacab.org/sutty/containers/nginx/).
|
||||
```json
|
||||
log_format main escape=json '{"host":"$host","msec":$msec,"server_protocol":"$server_protocol","request_method":"$request_method","request_completion":"$request_completion","uri":"$uri","query_string":"$query_string","status":$status,"sent_http_content_type":"$sent_http_content_type","sent_http_content_encoding":"$sent_http_content_encoding","sent_http_etag":"$sent_http_etag","sent_http_last_modified":"$sent_http_last_modified","http_accept":"$http_accept","http_accept_encoding":"$http_accept_encoding","http_accept_language":"$http_accept_language","http_pragma":"$http_pragma","http_cache_control":"$http_cache_control","http_if_none_match":"$http_if_none_match","http_dnt":"$http_dnt","http_user_agent":"$http_user_agent","http_origin":"$http_origin","http_referer":{"origin":"$http_origin","referrer":"$http_referer","policy":"origin-when-cross-origin"},"request_time":$request_time,"bytes_sent":$bytes_sent,"body_bytes_sent":$body_bytes_sent,"request_length":$request_length,"http_connection":"$http_connection","pipe":"$pipe","connection_requests":$connection_requests,"geoip2_data_country_name":"$geoip2_data_country_name","geoip2_data_city_name":"$geoip2_data_city_name","ssl_server_name":"$ssl_server_name","ssl_protocol":"$ssl_protocol","ssl_early_data":"$ssl_early_data","ssl_session_reused":"$ssl_session_reused","ssl_curves":"$ssl_curves","ssl_ciphers":"$ssl_ciphers","ssl_cipher":"$ssl_cipher","sent_http_x_xss_protection":"$sent_http_x_xss_protection","sent_http_x_frame_options":"$sent_http_x_frame_options","sent_http_x_content_type_options":"$sent_http_x_content_type_options","sent_http_strict_transport_security":"$sent_http_strict_transport_security","nginx_version":"$nginx_version","pid":"$pid","remote_user":""}';
|
||||
|
||||
access_log /var/log/nginx/access.log main;
|
||||
access_log syslog=unix:/tmp/access_log.socket,nohostname main;
|
||||
```
|
||||
|
||||
## Crawler user agents
|
||||
|
||||
+5
-10
@@ -1,16 +1,11 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Fail if there's no options provided
|
||||
# Fail if there are no options provided
|
||||
test -n "$ACCESS_LOGS_FLAGS" || exit 1
|
||||
|
||||
# Remove access.log and recreate it as a fifo
|
||||
rm -f /var/log/nginx/access.log /run/access_log.pid
|
||||
mkfifo /var/log/nginx/access.log
|
||||
chmod 755 /var/log/nginx/access.log
|
||||
|
||||
# Reopen log file
|
||||
nginx -s reload
|
||||
# Give write access to Nginx
|
||||
umask 007
|
||||
|
||||
# Read from fifo and load into database
|
||||
daemonize -p /run/access_logd.pid -u nobody \
|
||||
/bin/sh -c "cat /var/log/nginx/access.log | access_log $ACCESS_LOGS_FLAGS"
|
||||
daemonize -p /run/access_logd.pid -u nobody -g www-data \
|
||||
/usr/bin/access_log $ACCESS_LOGS_FLAGS
|
||||
|
||||
+24
-4
@@ -1,4 +1,6 @@
|
||||
require "file_utils"
|
||||
require "json"
|
||||
require "socket"
|
||||
require "sqlite3"
|
||||
require "pg"
|
||||
require "option_parser"
|
||||
@@ -6,8 +8,10 @@ require "uuid"
|
||||
require "./models/access_log"
|
||||
require "./models/crawler"
|
||||
|
||||
VERSION = "0.3.0"
|
||||
VERSION = "0.4.0"
|
||||
|
||||
# Default socket location
|
||||
socket = "/tmp/access_log.socket"
|
||||
# The default database URI
|
||||
database = "sqlite3://./development.sqlite3"
|
||||
# Detect web crawlers
|
||||
@@ -43,6 +47,10 @@ OptionParser.parse do |p|
|
||||
crawler = true
|
||||
crawlers = Array(Crawler).from_json File.read(c)
|
||||
end
|
||||
|
||||
p.on "-s /tmp/access_log.socket", "--socket /tmp/access_log.socket", "Listening socket" do |s|
|
||||
socket = s
|
||||
end
|
||||
end
|
||||
|
||||
# Parameterize values according to database URI
|
||||
@@ -65,20 +73,28 @@ if crawler
|
||||
crawler_re = Regex.union(crawlers.map { |c| c.pattern })
|
||||
end
|
||||
|
||||
# Just exit
|
||||
server = Socket.unix(Socket::Type::DGRAM)
|
||||
server.bind Socket::UNIXAddress.new(socket)
|
||||
|
||||
Signal::INT.trap do
|
||||
server.close
|
||||
FileUtils.rm(socket) if File.exists? socket
|
||||
exit
|
||||
end
|
||||
|
||||
# Same
|
||||
Signal::KILL.trap do
|
||||
server.close
|
||||
FileUtils.rm(socket) if File.exists? socket
|
||||
exit
|
||||
end
|
||||
|
||||
# Open the database and wait for JSONL input.
|
||||
db = DB.open database do |db|
|
||||
while (json = gets)
|
||||
while true
|
||||
begin
|
||||
msg, _ = server.receive(1024 * 64) # 64K
|
||||
_, _, _, _, json = msg.split(" ", 5)
|
||||
|
||||
# Parse input
|
||||
access_log = AccessLog.from_json(json || "{}")
|
||||
|
||||
@@ -137,6 +153,10 @@ db = DB.open database do |db|
|
||||
|
||||
# Ignore parsing errors
|
||||
rescue JSON::ParseException
|
||||
rescue IO::Error
|
||||
server.close
|
||||
FileUtils.rm(socket) if File.exists? socket
|
||||
exit
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user