はじめに

以下の記事で Go で自作したアクセスログパーサーについて紹介しました。

今回はこのモジュールを使って作成した CLI ツールについて紹介します。

概要

主に以下サービスのアクセスログを扱えます。

  • S3
  • CloudFront
  • ALB
  • NLB
  • CLB

変換する形式は以下に対応しています。

  • NDJSON (改行区切り JSON)
  • インデント付きの JSON
  • key=value 形式のテキスト

ログの渡し方は以下に対応しています。ZIP の場合はデフォルトで中身のエントリをすべて対象とします。

  • 文字列
  • ファイル
  • GZIP
  • ZIP

インストール

リリースページから OS ごとのバイナリをダウンロードできます。

使い方

ヘルプです。サブコマンドがサービス名になっています。

NAME:
   alpen - AWS log parser/encoder

USAGE:
   alpen [global options] command [command options] [arguments...]

VERSION:
   0.0.8

DESCRIPTION:
   A cli application for parsing AWS access logs

COMMANDS:
   s3   Parses S3 access logs
   cf   Parses CloudFront access logs
   alb  Parses ALB access logs
   nlb  Parses NLB access logs
   clb  Parses CLB access logs

GLOBAL OPTIONS:
   --completion value, -c value  select a shell to display completion scripts: bash|zsh|pwsh
   --help, -h                    show help
   --version, -v                 print the version

s3 サブコマンドのヘルプです。他のサブコマンドもオプションは同じです。

NAME:
   alpen s3 - Parses S3 access logs

USAGE:
   alpen s3

DESCRIPTION:
   Parses S3 access logs and converts them to structured formats

OPTIONS:
   --input value, -i value                            input from string
   --file-path value, -f value                        input from file path
   --gzip-path value, -g value                        input from gzip file path
   --zip-path value, -z value                         input from zip file path
   --output value, -o value                           select output format: text|json|pretty-json (default: "json")
   --skip value, -s value [ --skip value, -s value ]  skip records by index
   --metadata, -m                                     enable metadata output (default: false)
   --glob-pattern value, -G value                     filter glob pattern: available for parsing zip only (default: "*")
   --help, -h                                         show help

文字列で渡す

文字列で渡す場合は --input, -i を使います。なお、本記事のアクセスログはすべて AWS の公式ドキュメントに例示されているサンプルログを使用しています。

alpen s3 --input '79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be awsexamplebucket1 [06/Feb/2019:00:00:38 +0000] 192.0.2.3 79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be 3E57427F3EXAMPLE REST.GET.VERSIONING - "GET /awsexamplebucket1?versioning HTTP/1.1" 200 - 113 - 7 - "-" "S3Console/0.4" - s9lzHYrFp76ZVxRcpX9+5cjAnEH2ROuNkd2BHfIa6UkFVdtjf5mKR3/eTPFvsiP/XV/VLi31234= SigV2 ECDHE-RSA-AES128-GCM-SHA256 AuthHe
ader awsexamplebucket1.s3.us-west-1.amazonaws.com TLSV1.1'

デフォルトでは NDJSON 形式で出力されます。

{"index":1,"bucket_owner":"79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be","bucket":"awsexamplebucket1","time":"[06/Feb/2019:00:00:38 +0000]","remote_ip":"192.0.2.3","requester":"79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be","request_id":"3E57427F3EXAMPLE","operation":"REST.GET.VERSIONING","key":"-","request_uri":"GET /awsexamplebucket1?versioning HTTP/1.1","http_status":"200","error_code":"-","bytes_sent":"113","object_size":"-","total_time":"7","turn_around_time":"-","referer":"-","user_agent":"S3Console/0.4","version_id":"-","host_id":"s9lzHYrFp76ZVxRcpX9+5cjAnEH2ROuNkd2BHfIa6UkFVdtjf5mKR3/eTPFvsiP/XV/VLi31234=","signature_version":"SigV2","cipher_suite":"ECDHE-RSA-AES128-GCM-SHA256","authentication_type":"AuthHeader","host_header":"awsexamplebucket1.s3.us-west-1.amazonaws.com","tls_version":"TLSV1.1"}

ファイルパスで渡す

ファイルパスで渡す場合は --file-path, -f を使います。

alpen s3 --file-path testdata/log/sample_s3.log
{"index":1,"bucket_owner":"79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be","bucket":"awsexamplebucket1","time":"[06/Feb/2019:00:00:38 +0000]","remote_ip":"192.0.2.3","requester":"79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be","request_id":"3E57427F3EXAMPLE","operation":"REST.GET.VERSIONING","key":"-","request_uri":"GET /awsexamplebucket1?versioning HTTP/1.1","http_status":"200","error_code":"-","bytes_sent":"113","object_size":"-","total_time":"7","turn_around_time":"-","referer":"-","user_agent":"S3Console/0.4","version_id":"-","host_id":"s9lzHYrFp76ZVxRcpX9+5cjAnEH2ROuNkd2BHfIa6UkFVdtjf5mKR3/eTPFvsiP/XV/VLi31234=","signature_version":"SigV2","cipher_suite":"ECDHE-RSA-AES128-GCM-SHA256","authentication_type":"AuthHeader","host_header":"awsexamplebucket1.s3.us-west-1.amazonaws.com","tls_version":"TLSV1.1"}
{"index":2,"bucket_owner":"79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be","bucket":"awsexamplebucket1","time":"[06/Feb/2019:00:00:38 +0000]","remote_ip":"192.0.2.3","requester":"79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be","request_id":"891CE47D2EXAMPLE","operation":"REST.GET.LOGGING_STATUS","key":"-","request_uri":"GET /awsexamplebucket1?logging HTTP/1.1","http_status":"200","error_code":"-","bytes_sent":"242","object_size":"-","total_time":"11","turn_around_time":"-","referer":"-","user_agent":"S3Console/0.4","version_id":"-","host_id":"9vKBE6vMhrNiWHZmb2L0mXOcqPGzQOI5XLnCtZNPxev+Hf+7tpT6sxDwDty4LHBUOZJG96N1234=","signature_version":"SigV2","cipher_suite":"ECDHE-RSA-AES128-GCM-SHA256","authentication_type":"AuthHeader","host_header":"awsexamplebucket1.s3.us-west-1.amazonaws.com","tls_version":"TLSV1.1"}
{"index":3,"bucket_owner":"79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be","bucket":"awsexamplebucket1","time":"[06/Feb/2019:00:00:38 +0000]","remote_ip":"192.0.2.3","requester":"79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be","request_id":"A1206F460EXAMPLE","operation":"REST.GET.BUCKETPOLICY","key":"-","request_uri":"GET /awsexamplebucket1?policy HTTP/1.1","http_status":"404","error_code":"NoSuchBucketPolicy","bytes_sent":"297","object_size":"-","total_time":"38","turn_around_time":"-","referer":"-","user_agent":"S3Console/0.4","version_id":"-","host_id":"BNaBsXZQQDbssi6xMBdBU2sLt+Yf5kZDmeBUP35sFoKa3sLLeMC78iwEIWxs99CRUrbS4n11234=","signature_version":"SigV2","cipher_suite":"ECDHE-RSA-AES128-GCM-SHA256","authentication_type":"AuthHeader","host_header":"awsexamplebucket1.s3.us-west-1.amazonaws.com","tls_version":"TLSV1.1"}
{"index":4,"bucket_owner":"79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be","bucket":"awsexamplebucket1","time":"[06/Feb/2019:00:01:00 +0000]","remote_ip":"192.0.2.3","requester":"79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be","request_id":"7B4A0FABBEXAMPLE","operation":"REST.GET.VERSIONING","key":"-","request_uri":"GET /awsexamplebucket1?versioning HTTP/1.1","http_status":"200","error_code":"-","bytes_sent":"113","object_size":"-","total_time":"33","turn_around_time":"-","referer":"-","user_agent":"S3Console/0.4","version_id":"-","host_id":"Ke1bUcazaN1jWuUlPJaxF64cQVpUEhoZKEG/hmy/gijN/I1DeWqDfFvnpybfEseEME/u7ME1234=","signature_version":"SigV2","cipher_suite":"ECDHE-RSA-AES128-GCM-SHA256","authentication_type":"AuthHeader","host_header":"awsexamplebucket1.s3.us-west-1.amazonaws.com","tls_version":"TLSV1.1"}
{"index":5,"bucket_owner":"79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be","bucket":"awsexamplebucket1","time":"[06/Feb/2019:00:01:57 +0000]","remote_ip":"192.0.2.3","requester":"79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be","request_id":"DD6CC733AEXAMPLE","operation":"REST.PUT.OBJECT","key":"s3-dg.pdf","request_uri":"PUT /awsexamplebucket1/s3-dg.pdf HTTP/1.1","http_status":"200","error_code":"-","bytes_sent":"-","object_size":"4406583","total_time":"41754","turn_around_time":"28","referer":"-","user_agent":"S3Console/0.4","version_id":"-","host_id":"10S62Zv81kBW7BB6SX4XJ48o6kpcl6LPwEoizZQQxJd5qDSCTLX0TgS37kYUBKQW3+bPdrg1234=","signature_version":"SigV4","cipher_suite":"ECDHE-RSA-AES128-SHA","authentication_type":"AuthHeader","host_header":"awsexamplebucket1.s3.us-west-1.amazonaws.com","tls_version":"TLSV1.1"}

メタデータも表示する

メタデータ (簡単な集計情報とアンマッチデータの情報) も出力するには --metadata, -m オプションを使います。

alpen s3 --file-path testdata/log/sample_s3.log -m
# 省略
{"total":5,"matched":5,"unmatched":0,"skipped":0,"source":"sample_s3.log","errors":null}

key=value 形式で表示する

出力形式は --output, -o で設定します。使えるのは text, json, pretty-json のいずれかで、デフォルトは json です。

text の場合。あまり使い道はないかもしれません。

alpen s3 --file-path testdata/log/sample_s3.log -m -o text
index=1 bucket_owner="79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be" bucket="awsexamplebucket1" time="[06/Feb/2019:00:00:38 +0000]" remote_ip="192.0.2.3" requester="79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be" request_id="3E57427F3EXAMPLE" operation="REST.GET.VERSIONING" key="-" request_uri="GET /awsexamplebucket1?versioning HTTP/1.1" http_status="200" error_code="-" bytes_sent="113" object_size="-" total_time="7" turn_around_time="-" referer="-" user_agent="S3Console/0.4" version_id="-" host_id="s9lzHYrFp76ZVxRcpX9+5cjAnEH2ROuNkd2BHfIa6UkFVdtjf5mKR3/eTPFvsiP/XV/VLi31234=" signature_version="SigV2" cipher_suite="ECDHE-RSA-AES128-GCM-SHA256" authentication_type="AuthHeader" host_header="awsexamplebucket1.s3.us-west-1.amazonaws.com" tls_version="TLSV1.1"
index=2 bucket_owner="79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be" bucket="awsexamplebucket1" time="[06/Feb/2019:00:00:38 +0000]" remote_ip="192.0.2.3" requester="79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be" request_id="891CE47D2EXAMPLE" operation="REST.GET.LOGGING_STATUS" key="-" request_uri="GET /awsexamplebucket1?logging HTTP/1.1" http_status="200" error_code="-" bytes_sent="242" object_size="-" total_time="11" turn_around_time="-" referer="-" user_agent="S3Console/0.4" version_id="-" host_id="9vKBE6vMhrNiWHZmb2L0mXOcqPGzQOI5XLnCtZNPxev+Hf+7tpT6sxDwDty4LHBUOZJG96N1234=" signature_version="SigV2" cipher_suite="ECDHE-RSA-AES128-GCM-SHA256" authentication_type="AuthHeader" host_header="awsexamplebucket1.s3.us-west-1.amazonaws.com" tls_version="TLSV1.1"
index=3 bucket_owner="79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be" bucket="awsexamplebucket1" time="[06/Feb/2019:00:00:38 +0000]" remote_ip="192.0.2.3" requester="79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be" request_id="A1206F460EXAMPLE" operation="REST.GET.BUCKETPOLICY" key="-" request_uri="GET /awsexamplebucket1?policy HTTP/1.1" http_status="404" error_code="NoSuchBucketPolicy" bytes_sent="297" object_size="-" total_time="38" turn_around_time="-" referer="-" user_agent="S3Console/0.4" version_id="-" host_id="BNaBsXZQQDbssi6xMBdBU2sLt+Yf5kZDmeBUP35sFoKa3sLLeMC78iwEIWxs99CRUrbS4n11234=" signature_version="SigV2" cipher_suite="ECDHE-RSA-AES128-GCM-SHA256" authentication_type="AuthHeader" host_header="awsexamplebucket1.s3.us-west-1.amazonaws.com" tls_version="TLSV1.1"
index=4 bucket_owner="79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be" bucket="awsexamplebucket1" time="[06/Feb/2019:00:01:00 +0000]" remote_ip="192.0.2.3" requester="79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be" request_id="7B4A0FABBEXAMPLE" operation="REST.GET.VERSIONING" key="-" request_uri="GET /awsexamplebucket1?versioning HTTP/1.1" http_status="200" error_code="-" bytes_sent="113" object_size="-" total_time="33" turn_around_time="-" referer="-" user_agent="S3Console/0.4" version_id="-" host_id="Ke1bUcazaN1jWuUlPJaxF64cQVpUEhoZKEG/hmy/gijN/I1DeWqDfFvnpybfEseEME/u7ME1234=" signature_version="SigV2" cipher_suite="ECDHE-RSA-AES128-GCM-SHA256" authentication_type="AuthHeader" host_header="awsexamplebucket1.s3.us-west-1.amazonaws.com" tls_version="TLSV1.1"
index=5 bucket_owner="79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be" bucket="awsexamplebucket1" time="[06/Feb/2019:00:01:57 +0000]" remote_ip="192.0.2.3" requester="79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be" request_id="DD6CC733AEXAMPLE" operation="REST.PUT.OBJECT" key="s3-dg.pdf" request_uri="PUT /awsexamplebucket1/s3-dg.pdf HTTP/1.1" http_status="200" error_code="-" bytes_sent="-" object_size="4406583" total_time="41754" turn_around_time="28" referer="-" user_agent="S3Console/0.4" version_id="-" host_id="10S62Zv81kBW7BB6SX4XJ48o6kpcl6LPwEoizZQQxJd5qDSCTLX0TgS37kYUBKQW3+bPdrg1234=" signature_version="SigV4" cipher_suite="ECDHE-RSA-AES128-SHA" authentication_type="AuthHeader" host_header="awsexamplebucket1.s3.us-west-1.amazonaws.com" tls_version="TLSV1.1"
total=5 matched=5 unmatched=0 skipped=0 source="sample_s3.log" errors=null

インデントされた JSON で表示する

pretty-json の場合。手元で調査する場合にいちばん実用的かと思います。

{
  "index": 1,
  "bucket_owner": "79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be",
  "bucket": "awsexamplebucket1",
  "time": "[06/Feb/2019:00:00:38 +0000]",
  "remote_ip": "192.0.2.3",
  "requester": "79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be",
  "request_id": "3E57427F3EXAMPLE",
  "operation": "REST.GET.VERSIONING",
  "key": "-",
  "request_uri": "GET /awsexamplebucket1?versioning HTTP/1.1",
  "http_status": "200",
  "error_code": "-",
  "bytes_sent": "113",
  "object_size": "-",
  "total_time": "7",
  "turn_around_time": "-",
  "referer": "-",
  "user_agent": "S3Console/0.4",
  "version_id": "-",
  "host_id": "s9lzHYrFp76ZVxRcpX9+5cjAnEH2ROuNkd2BHfIa6UkFVdtjf5mKR3/eTPFvsiP/XV/VLi31234=",
  "signature_version": "SigV2",
  "cipher_suite": "ECDHE-RSA-AES128-GCM-SHA256",
  "authentication_type": "AuthHeader",
  "host_header": "awsexamplebucket1.s3.us-west-1.amazonaws.com",
  "tls_version": "TLSV1.1"
}
{
  "index": 2,
  "bucket_owner": "79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be",
  "bucket": "awsexamplebucket1",
  "time": "[06/Feb/2019:00:00:38 +0000]",
  "remote_ip": "192.0.2.3",
  "requester": "79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be",
  "request_id": "891CE47D2EXAMPLE",
  "operation": "REST.GET.LOGGING_STATUS",
  "key": "-",
  "request_uri": "GET /awsexamplebucket1?logging HTTP/1.1",
  "http_status": "200",
  "error_code": "-",
  "bytes_sent": "242",
  "object_size": "-",
  "total_time": "11",
  "turn_around_time": "-",
  "referer": "-",
  "user_agent": "S3Console/0.4",
  "version_id": "-",
  "host_id": "9vKBE6vMhrNiWHZmb2L0mXOcqPGzQOI5XLnCtZNPxev+Hf+7tpT6sxDwDty4LHBUOZJG96N1234=",
  "signature_version": "SigV2",
  "cipher_suite": "ECDHE-RSA-AES128-GCM-SHA256",
  "authentication_type": "AuthHeader",
  "host_header": "awsexamplebucket1.s3.us-west-1.amazonaws.com",
  "tls_version": "TLSV1.1"
}
{
  "index": 3,
  "bucket_owner": "79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be",
  "bucket": "awsexamplebucket1",
  "time": "[06/Feb/2019:00:00:38 +0000]",
  "remote_ip": "192.0.2.3",
  "requester": "79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be",
  "request_id": "A1206F460EXAMPLE",
  "operation": "REST.GET.BUCKETPOLICY",
  "key": "-",
  "request_uri": "GET /awsexamplebucket1?policy HTTP/1.1",
  "http_status": "404",
  "error_code": "NoSuchBucketPolicy",
  "bytes_sent": "297",
  "object_size": "-",
  "total_time": "38",
  "turn_around_time": "-",
  "referer": "-",
  "user_agent": "S3Console/0.4",
  "version_id": "-",
  "host_id": "BNaBsXZQQDbssi6xMBdBU2sLt+Yf5kZDmeBUP35sFoKa3sLLeMC78iwEIWxs99CRUrbS4n11234=",
  "signature_version": "SigV2",
  "cipher_suite": "ECDHE-RSA-AES128-GCM-SHA256",
  "authentication_type": "AuthHeader",
  "host_header": "awsexamplebucket1.s3.us-west-1.amazonaws.com",
  "tls_version": "TLSV1.1"
}
{
  "index": 4,
  "bucket_owner": "79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be",
  "bucket": "awsexamplebucket1",
  "time": "[06/Feb/2019:00:01:00 +0000]",
  "remote_ip": "192.0.2.3",
  "requester": "79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be",
  "request_id": "7B4A0FABBEXAMPLE",
  "operation": "REST.GET.VERSIONING",
  "key": "-",
  "request_uri": "GET /awsexamplebucket1?versioning HTTP/1.1",
  "http_status": "200",
  "error_code": "-",
  "bytes_sent": "113",
  "object_size": "-",
  "total_time": "33",
  "turn_around_time": "-",
  "referer": "-",
  "user_agent": "S3Console/0.4",
  "version_id": "-",
  "host_id": "Ke1bUcazaN1jWuUlPJaxF64cQVpUEhoZKEG/hmy/gijN/I1DeWqDfFvnpybfEseEME/u7ME1234=",
  "signature_version": "SigV2",
  "cipher_suite": "ECDHE-RSA-AES128-GCM-SHA256",
  "authentication_type": "AuthHeader",
  "host_header": "awsexamplebucket1.s3.us-west-1.amazonaws.com",
  "tls_version": "TLSV1.1"
}
{
  "index": 5,
  "bucket_owner": "79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be",
  "bucket": "awsexamplebucket1",
  "time": "[06/Feb/2019:00:01:57 +0000]",
  "remote_ip": "192.0.2.3",
  "requester": "79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be",
  "request_id": "DD6CC733AEXAMPLE",
  "operation": "REST.PUT.OBJECT",
  "key": "s3-dg.pdf",
  "request_uri": "PUT /awsexamplebucket1/s3-dg.pdf HTTP/1.1",
  "http_status": "200",
  "error_code": "-",
  "bytes_sent": "-",
  "object_size": "4406583",
  "total_time": "41754",
  "turn_around_time": "28",
  "referer": "-",
  "user_agent": "S3Console/0.4",
  "version_id": "-",
  "host_id": "10S62Zv81kBW7BB6SX4XJ48o6kpcl6LPwEoizZQQxJd5qDSCTLX0TgS37kYUBKQW3+bPdrg1234=",
  "signature_version": "SigV4",
  "cipher_suite": "ECDHE-RSA-AES128-SHA",
  "authentication_type": "AuthHeader",
  "host_header": "awsexamplebucket1.s3.us-west-1.amazonaws.com",
  "tls_version": "TLSV1.1"
}
{
  "total": 5,
  "matched": 5,
  "unmatched": 0,
  "skipped": 0,
  "source": "sample_s3.log",
  "errors": null
}

特定の行を読み飛ばす

ヘッダーを読み飛ばしたい場合などは --skip, -s オプションが使えます。行番号をカンマ区切りで渡します。

alpen cf --file-path testdata/log/sample_cf.log --skip 1,2

ZIP の中身を直接解析する

ZIP を直接読む場合は --zip-path, -z でパスを渡します。中身のファイルをすべて読ませたくない場合は、--glob-pattern, -G オプションを使って除外できます。

alpen alb --zip-path testdata/zip/sample_alb.log.zip --glob-pattern *.log

シェル補完について

シェル補完にも対応しています。対応しているシェルは bash, zsh, pwsh です。以下のコマンドでスクリプトが標準出力に表示されるのでファイルにリダイレクトしておき、シェル起動時に所定の方法で読み込ませてください。

alpen --completion bash

コマンドラインパーサーについて

コマンドラインでの引数解析を実現するためのライブラリとして、今回 github.com/urfave/cli/v2 を使いました。多少オーバースペックかなと思いましたが、簡単に CLI アプリを実現できて便利でした。シェル補完スクリプトの対応もこのライブラリの機能を利用しています。

正規表現パターン

解析のための正規表現パターンはコードを見ていただいたほうがよいかと思います。

こちら

今後の展望

実現難易度はまだ調べていませんが、以下をサポートできればと考えています。手元でアクセスログ調査を行う際の汎用的なツールという位置付けにできればいいなと思っています。

  • Apache common log format
  • Apache common log format (with vhost)
  • Apache combined log format

また、外部ファイルから正規表現パターンを読ませる機能があれば独自のログ形式を解析できますし、text/template を使えば出力もカスタマイズできるはずなので、作る側の気力次第では割と汎用的なツールに育つかもしれません。

おわりに

AWS のアクセスログを解析する CLI ツールについて紹介しました。まだ破壊的変更が入る可能性はありますが、よろしければ使ってください。
このツールの作成を通じて Go 製 CLI アプリの CI 手法やバイナリのリリースを自動化する方法も学べたので、そちらも別記事で紹介したいと思います。