Skip to content

Commit 42c5866

Browse files
committed
Log the client ip_address on transaction_profiling_analyzer audit entries
1 parent fd7f1ac commit 42c5866

2 files changed

Lines changed: 56 additions & 8 deletions

File tree

‎contrib/transaction_profiling_analyzer/transaction_profiling_analyzer.py‎

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
from json import JSONEncoder
3838
import logging
3939
import os
40+
import socket
4041
import struct
4142
import sys
4243
import uuid
@@ -823,14 +824,38 @@ def printable(b):
823824
return "".join("\\\\" if c == 92 else chr(c) if 32 <= c < 127 else "\\x%02x" % c for c in b)
824825

825826

826-
def cluster_description(cluster_file):
827-
"""The description part of the cluster file's connection string, looked up the same way fdb.open finds the file."""
827+
def connection_string(cluster_file):
828+
"""The cluster file's connection string, looked up the same way fdb.open finds the file."""
828829
for path in [cluster_file, os.environ.get("FDB_CLUSTER_FILE"), "fdb.cluster",
829830
"/etc/foundationdb/fdb.cluster", "/usr/local/etc/foundationdb/fdb.cluster"]:
830831
if path and os.path.exists(path):
831832
with open(path) as f:
832833
lines = [line.strip() for line in f if line.strip() and not line.startswith("#")]
833-
return lines[-1].split(":")[0]
834+
return lines[-1]
835+
return ""
836+
837+
838+
def cluster_description(cluster_file):
839+
"""The description part of the connection string, description:id@coordinators."""
840+
return connection_string(cluster_file).split(":")[0]
841+
842+
843+
def local_ip(cluster_file):
844+
"""The local IP this client reaches the coordinators from, i.e. what the OS picks for a UDP socket aimed at a
845+
coordinator (nothing is sent). Same way fdb picks the IP for client trace files."""
846+
coordinators = connection_string(cluster_file).partition("@")[2]
847+
for coordinator in coordinators.split(","):
848+
address = coordinator.strip()
849+
if address.endswith(":tls"):
850+
address = address[:-len(":tls")]
851+
host, _, port = address.rpartition(":")
852+
try:
853+
for family, _, _, _, sockaddr in socket.getaddrinfo(host.strip("[]"), int(port), type=socket.SOCK_DGRAM):
854+
with socket.socket(family, socket.SOCK_DGRAM) as s:
855+
s.connect(sockaddr)
856+
return s.getsockname()[0]
857+
except (OSError, ValueError):
858+
continue
834859
return ""
835860

836861

@@ -852,10 +877,11 @@ def audit_log_path():
852877
class AuditLog(object):
853878
"""Appends one JSON line per output the tool prints. Entries from the same input share a reqid."""
854879

855-
def __init__(self, path, cluster):
880+
def __init__(self, path, cluster, ip_address=""):
856881
open(path, "a").close() # fail at startup, not mid-run, if the file isn't writable
857882
self.path = path
858883
self.cluster = cluster
884+
self.ip_address = ip_address
859885

860886
@staticmethod
861887
def new_reqid():
@@ -867,6 +893,7 @@ def log(self, reqid, operation, input, **output):
867893
"level": "info",
868894
"msg": "TransactionProfilingAnalyzerAudit",
869895
"cluster": self.cluster,
896+
"ip_address": self.ip_address,
870897
"reqid": reqid,
871898
"operation": operation,
872899
"input": input,
@@ -948,7 +975,7 @@ def main():
948975

949976
audit = None
950977
if args.audit_log or "FDB_TRANSACTION_PROFILER_AUDIT_LOG_ENABLE" in os.environ:
951-
audit = AuditLog(audit_log_path(), cluster_description(args.cluster_file))
978+
audit = AuditLog(audit_log_path(), cluster_description(args.cluster_file), local_ip(args.cluster_file))
952979

953980
type_filter = set()
954981
if args.filter_get_version:

‎contrib/transaction_profiling_analyzer/transaction_profiling_analyzer_audit_tests.py‎

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,15 @@
2626
from types import SimpleNamespace as Obj
2727
from unittest import mock
2828

29-
from transaction_profiling_analyzer import AuditLog, KeyRange, Mutation, audit_log_path, cluster_description, printable
29+
from transaction_profiling_analyzer import (
30+
AuditLog,
31+
KeyRange,
32+
Mutation,
33+
audit_log_path,
34+
cluster_description,
35+
local_ip,
36+
printable,
37+
)
3038

3139

3240
def read_log(path):
@@ -61,16 +69,29 @@ def test_cluster_description(self):
6169
f.write("# DO NOT EDIT!\nmycluster:abc@127.0.0.1:4500\n")
6270
self.assertEqual(cluster_description(self.path), "mycluster")
6371

72+
def test_local_ip(self):
73+
for coordinators, expected in [
74+
("127.0.0.1:4500", {"127.0.0.1"}),
75+
("127.0.0.1:4500:tls", {"127.0.0.1"}),
76+
("[::1]:4500", {"::1"}),
77+
("localhost:4500", {"127.0.0.1", "::1"}),
78+
("bad,127.0.0.1:4500", {"127.0.0.1"}), # unusable coordinators are skipped
79+
]:
80+
with open(self.path, "w") as f:
81+
f.write("c:id@%s\n" % coordinators)
82+
self.assertIn(local_ip(self.path), expected, coordinators)
83+
6484
def test_log_appends_one_json_line_per_entry(self):
65-
AuditLog(self.path, "c1").log("r1", "top_writes", {"top_requests": 1}, key=b"a", count=3)
85+
AuditLog(self.path, "c1", "10.0.0.1").log("r1", "top_writes", {"top_requests": 1}, key=b"a", count=3)
6686
AuditLog(self.path, "c1").log("r2", "top_writes", {"top_requests": 1}, key=b"b", count=1)
6787
records = read_log(self.path)
6888
self.assertEqual([r["key"] for r in records], ["a", "b"])
6989
self.assertEqual(
7090
sorted(records[0]),
71-
["cluster", "count", "input", "key", "level", "msg", "operation", "reqid", "ts"],
91+
["cluster", "count", "input", "ip_address", "key", "level", "msg", "operation", "reqid", "ts"],
7292
)
7393
self.assertEqual(records[0]["cluster"], "c1")
94+
self.assertEqual(records[0]["ip_address"], "10.0.0.1")
7495
self.assertRegex(records[0]["ts"], r"^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\dZ$")
7596

7697
def test_log_transaction(self):

0 commit comments

Comments
 (0)