Compare commits
15 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
0a601d4e9f | |
|
|
fa54fb58ae | |
|
|
cfc544e3d7 | |
|
|
1620ed61a3 | |
|
|
0a49462733 | |
|
|
1cdb9f6022 | |
|
|
adf07c991c | |
|
|
27e82d3705 | |
|
|
ee921626ff | |
|
|
c8f685c660 | |
|
|
481df74419 | |
|
|
0d4e7505f6 | |
|
|
95e46ec26e | |
|
|
2f49f1c23f | |
|
|
3fc983f28f |
|
|
@ -160,6 +160,6 @@ cython_debug/
|
||||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||||
#.idea/
|
.idea/
|
||||||
|
|
||||||
# End of https://www.toptal.com/developers/gitignore/api/python
|
# End of https://www.toptal.com/developers/gitignore/api/python
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
<content url="file://$MODULE_DIR$">
|
<content url="file://$MODULE_DIR$">
|
||||||
<excludeFolder url="file://$MODULE_DIR$/.venv" />
|
<excludeFolder url="file://$MODULE_DIR$/.venv" />
|
||||||
</content>
|
</content>
|
||||||
<orderEntry type="inheritedJdk" />
|
<orderEntry type="jdk" jdkName="Python 3.10 (civiproxy_logs2json) (2)" jdkType="Python SDK" />
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
</component>
|
</component>
|
||||||
<component name="PyNamespacePackagesService">
|
<component name="PyNamespacePackagesService">
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.10 (civiproxy_logs2json)" project-jdk-type="Python SDK" />
|
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.10 (civiproxy_logs2json) (2)" project-jdk-type="Python SDK" />
|
||||||
</project>
|
</project>
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
include src/civiproxy_logs2json/*.toml
|
||||||
11
README.md
11
README.md
|
|
@ -9,9 +9,9 @@ python3 -m pip install civiproxy_logs2json --user
|
||||||
|
|
||||||
## Example Usage
|
## Example Usage
|
||||||
|
|
||||||
Pass logfile as positional argument:
|
Pass logfile as option:
|
||||||
```bash
|
```bash
|
||||||
cpl2j /var/www/proxy_logs/proxy.log
|
cpl2j -f /var/www/proxy_logs/proxy.log
|
||||||
```
|
```
|
||||||
|
|
||||||
Pipe logfile into program:
|
Pipe logfile into program:
|
||||||
|
|
@ -24,9 +24,14 @@ Set JSON indentation to two spaces:
|
||||||
cat proxy.log | cpl2j -s 2
|
cat proxy.log | cpl2j -s 2
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Output JSON lines:
|
||||||
|
```bash
|
||||||
|
cpl2j -l -f /var/www/proxy_logs/proxy.log
|
||||||
|
```
|
||||||
|
|
||||||
### Tip
|
### Tip
|
||||||
Use [VisiData](https://github.com/saulpw/visidata) to explore the data in a very comfortable way:
|
Use [VisiData](https://github.com/saulpw/visidata) to explore the data in a very comfortable way:
|
||||||
/var/www/proxy_logs/proxy.log
|
/var/www/proxy_logs/proxy.log
|
||||||
```bash
|
```bash
|
||||||
cpl2j /var/www/proxy_logs/proxy.log | vd -f json
|
cpl2j -l -f /var/www/proxy_logs/proxy.log | vd -f jsonl
|
||||||
```
|
```
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
from .__main__ import main, translate_logfile
|
|
||||||
|
|
@ -1,63 +0,0 @@
|
||||||
import argparse
|
|
||||||
import json
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from typing import TextIO
|
|
||||||
|
|
||||||
REQUEST_LINE_RE = r"^REQUEST FROM (?P<source>(\d{1,3}\.){3}\d{1,3}) ON (?P<date>\d\d\d\d-\d\d-\d\d) (?P<time>\d\d:\d\d:\d\d) -- Array$"
|
|
||||||
VALUES_LINE_RE = r"^\s{4}\[(?P<key>.*)] => (?P<value>.*)$"
|
|
||||||
CLOSING_LINE_RE = r"^\)$"
|
|
||||||
|
|
||||||
# Setup argparse
|
|
||||||
argparser = argparse.ArgumentParser(
|
|
||||||
description='Translate a CiviProxy logfile into JSON format. ')
|
|
||||||
argparser.add_argument('-f',
|
|
||||||
'--logfile',
|
|
||||||
help='CiviProxy logfile',
|
|
||||||
type=argparse.FileType('r', encoding='UTF-8'),
|
|
||||||
default=(None if sys.stdin.isatty() else sys.stdin))
|
|
||||||
argparser.add_argument('-i',
|
|
||||||
'--indent',
|
|
||||||
help='number of spaces to indent JSON output',
|
|
||||||
type=int,
|
|
||||||
default=4)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
args = argparser.parse_args()
|
|
||||||
|
|
||||||
# Print info if no logfile is specified or passed via stdin
|
|
||||||
if not args.logfile:
|
|
||||||
print('Please specify a path to a CiviProxy logfile')
|
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
# Parse logfile and print it to console
|
|
||||||
print(json.dumps(translate_logfile(args.logfile), indent=args.indent))
|
|
||||||
|
|
||||||
|
|
||||||
def translate_logfile(logfile: TextIO) -> list:
|
|
||||||
json_ = []
|
|
||||||
with logfile as file:
|
|
||||||
array = {}
|
|
||||||
for line in file:
|
|
||||||
request_line = re.search(REQUEST_LINE_RE, line)
|
|
||||||
values_line = re.search(VALUES_LINE_RE, line)
|
|
||||||
close_line = re.search(CLOSING_LINE_RE, line)
|
|
||||||
if request_line:
|
|
||||||
array["date"] = request_line.group("date")
|
|
||||||
array["time"] = request_line.group("time")
|
|
||||||
array["source"] = request_line.group("source")
|
|
||||||
elif values_line:
|
|
||||||
if values_line.group("key") == "json":
|
|
||||||
array["values"] = json.loads(values_line.group("value"))
|
|
||||||
else:
|
|
||||||
array[values_line.group("key")] = values_line.group("value")
|
|
||||||
elif close_line:
|
|
||||||
if array:
|
|
||||||
json_.append(array)
|
|
||||||
array = {}
|
|
||||||
return json_
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
[build-system]
|
||||||
|
requires = ["setuptools>=61.0.0", "wheel"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "civiproxy_logs2json"
|
||||||
|
version = "1.1.0"
|
||||||
|
description = "Translate a CiviProxy logfile into JSON format."
|
||||||
|
readme = "README.md"
|
||||||
|
authors = [{ name = "Marc Michalsky", email = "michalsky@forumZFD.de" }]
|
||||||
|
license = { file = "LICENSE" }
|
||||||
|
classifiers = [
|
||||||
|
"License :: OSI Approved :: MIT License",
|
||||||
|
"Programming Language :: Python",
|
||||||
|
"Programming Language :: Python :: 3",
|
||||||
|
]
|
||||||
|
keywords = ["civicrm", "json", "logs"]
|
||||||
|
dependencies = ['tomli; python_version < "3.11"',]
|
||||||
|
requires-python = ">=3.5"
|
||||||
|
|
||||||
|
[project.optional-dependencies]
|
||||||
|
dev = ["pytest", "bumpver"]
|
||||||
|
|
||||||
|
[project.urls]
|
||||||
|
Homepage = "https://github.com/MarcMichalsky/civiproxy_logs2json"
|
||||||
|
|
||||||
|
[project.scripts]
|
||||||
|
cpl2j = "civiproxy_logs2json.__main__:main"
|
||||||
|
|
||||||
|
[tool.bumpver]
|
||||||
|
current_version = "1.1.0"
|
||||||
|
version_pattern = "MAJOR.MINOR.PATCH"
|
||||||
|
commit_message = "🔖 bump version {old_version} -> {new_version}"
|
||||||
|
commit = true
|
||||||
|
tag = true
|
||||||
|
push = false
|
||||||
|
|
||||||
|
[tool.bumpver.file_patterns]
|
||||||
|
"pyproject.toml" = [
|
||||||
|
'"{version}"',
|
||||||
|
]
|
||||||
|
"src/civiproxy_logs2json/__init__.py" = [
|
||||||
|
"{version}"
|
||||||
|
]
|
||||||
24
setup.py
24
setup.py
|
|
@ -1,23 +1,3 @@
|
||||||
from setuptools import setup, find_packages
|
from setuptools import setup
|
||||||
|
|
||||||
with open("README.md", "r") as fh:
|
setup()
|
||||||
description = fh.read()
|
|
||||||
|
|
||||||
setup(
|
|
||||||
name="civiproxy_logs2json",
|
|
||||||
version="1.0.3",
|
|
||||||
author="Marc Michalsky",
|
|
||||||
author_email="michalsky@forumZFD.de",
|
|
||||||
packages=find_packages("civiproxy_logs2json", exclude=["test"]),
|
|
||||||
description="Translate a CiviProxy logfile into JSON format.",
|
|
||||||
long_description=description,
|
|
||||||
long_description_content_type="text/markdown",
|
|
||||||
url="https://github.com/MarcMichalsky/civiproxy_logs2json",
|
|
||||||
license='MIT',
|
|
||||||
python_requires='>=3.5',
|
|
||||||
entry_points={
|
|
||||||
'console_scripts': [
|
|
||||||
'cpl2j = civiproxy_logs2json.__main__:main',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
from importlib import resources
|
||||||
|
try:
|
||||||
|
import tomllib
|
||||||
|
except ModuleNotFoundError:
|
||||||
|
import tomli as tomllib
|
||||||
|
|
||||||
|
__version__ = "1.1.0"
|
||||||
|
|
||||||
|
_cfg = tomllib.loads(resources.read_text("civiproxy_logs2json", "config.toml"))
|
||||||
|
REQUEST_LINE_RE = _cfg["regex"]["request_line"]
|
||||||
|
VALUES_LINE_RE = _cfg["regex"]["values_line"]
|
||||||
|
CLOSING_LINE_RE = _cfg["regex"]["closing_line"]
|
||||||
|
|
@ -0,0 +1,74 @@
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from typing import TextIO
|
||||||
|
|
||||||
|
from . import REQUEST_LINE_RE, VALUES_LINE_RE, CLOSING_LINE_RE
|
||||||
|
|
||||||
|
|
||||||
|
# Setup argparse
|
||||||
|
argparser = argparse.ArgumentParser(
|
||||||
|
description="Translate a CiviProxy logfile into JSON Lines format.")
|
||||||
|
argparser.add_argument("-f",
|
||||||
|
"--logfile",
|
||||||
|
help="CiviProxy logfile",
|
||||||
|
type=argparse.FileType("r", encoding="UTF-8"),
|
||||||
|
default=(None if sys.stdin.isatty() else sys.stdin))
|
||||||
|
argparser.add_argument("-i",
|
||||||
|
"--indent",
|
||||||
|
help="number of spaces to indent JSON output",
|
||||||
|
type=int,
|
||||||
|
default=None)
|
||||||
|
argparser.add_argument("-l",
|
||||||
|
"--lines",
|
||||||
|
help="output JSON Lines instead of JSON",
|
||||||
|
action="store_true")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
args = argparser.parse_args()
|
||||||
|
|
||||||
|
# Print info if no logfile is specified or passed via stdin
|
||||||
|
if not args.logfile:
|
||||||
|
print("Please specify a path to a CiviProxy logfile")
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
# Parse logfile and print it to console
|
||||||
|
for line in translate_logfile(args.logfile,
|
||||||
|
indent=args.indent,
|
||||||
|
json_lines=args.lines):
|
||||||
|
print(line)
|
||||||
|
|
||||||
|
|
||||||
|
def translate_logfile(logfile: TextIO, indent: int, json_lines: bool) -> str:
|
||||||
|
_json = []
|
||||||
|
array = {}
|
||||||
|
with logfile as file:
|
||||||
|
for line in file:
|
||||||
|
request_line = re.search(REQUEST_LINE_RE, line)
|
||||||
|
values_line = re.search(VALUES_LINE_RE, line)
|
||||||
|
close_line = re.search(CLOSING_LINE_RE, line)
|
||||||
|
if request_line:
|
||||||
|
array["date"] = request_line.group("date")
|
||||||
|
array["time"] = request_line.group("time")
|
||||||
|
array["source"] = request_line.group("source")
|
||||||
|
elif values_line:
|
||||||
|
if values_line.group("key") == "json":
|
||||||
|
array["values"] = json.loads(values_line.group("value"))
|
||||||
|
else:
|
||||||
|
array[values_line.group("key")] = values_line.group("value")
|
||||||
|
elif close_line:
|
||||||
|
if array:
|
||||||
|
if json_lines:
|
||||||
|
yield json.dumps(array, indent=indent)
|
||||||
|
array = {}
|
||||||
|
else:
|
||||||
|
_json.append(array)
|
||||||
|
array = {}
|
||||||
|
if not json_lines:
|
||||||
|
yield json.dumps(_json, indent=indent)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
[regex]
|
||||||
|
request_line = "^REQUEST FROM (?P<source>(\\d{1,3}\\.){3}\\d{1,3}) ON (?P<date>\\d\\d\\d\\d-\\d\\d-\\d\\d) (?P<time>\\d\\d:\\d\\d:\\d\\d) -- Array$"
|
||||||
|
values_line = "^\\s{4}\\[(?P<key>.*)] => (?P<value>.*)$"
|
||||||
|
closing_line = "^\\)$"
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import os
|
import os
|
||||||
import json
|
import json
|
||||||
from civiproxy_logs2json import translate_logfile
|
from src.civiproxy_logs2json.__main__ import translate_logfile
|
||||||
|
|
||||||
|
|
||||||
def test_translate_logfile():
|
def test_translate_logfile():
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue