]>
gitweb.michael.orlitzky.com - djbdns-logparse.git/blob - bin/djbdns-logparse
6cb702fc084ede27fd4271dc4226f0469fa5ea1b
3 # Reads log files from tinydns and/or dnscache and prints them out in
4 # human-readable form. Logs can be supplied on stdin, or listed on the
7 # $ cat @*.s | djbdns-logparse
8 # $ djbdns-logparse @*.s
9 # $ tail -f current | djbdns-logparse
11 # Pipes each log file through tai64nlocal, which must be on your path.
15 # * The log format descriptions by Rob Mayoff were invaluable:
16 # ** http://dqd.com/~mayoff/notes/djbdns/tinydns-log.html
17 # ** http://dqd.com/~mayoff/notes/djbdns/dnscache-log.html
19 # * Faried Nawaz's dnscache log parser was the original inspiration:
20 # ** http://www.hungry.com/~fn/dnscache-log.pl.txt
24 from struct
import pack
25 from time
import strftime
, gmtime
26 from subprocess
import Popen
, PIPE
28 # common components of line-matching regexes
29 timestamp_pat
= r
'[\d-]+ [\d:\.]+' # output of tai64nlocal
30 hex4_pat
= r
'[0-9a-f]{4}'
31 ip_pat
= r
'[0-9a-f]{8,32}' # IPv4 or IPv6 addresses in hex
33 # discriminate between dnscache and tinydns log lines
34 tinydns_log_re
= re
.compile(
35 r
'(%s) (%s):(%s):(%s) ([\+\-IC/]) (%s) (.*)'
36 % (timestamp_pat
, ip_pat
, hex4_pat
, hex4_pat
, hex4_pat
))
37 dnscache_log_re
= re
.compile(r
'(%s) (\w+)(.*)' % timestamp_pat
)
65 def warn(filename
, msg
):
66 sys
.stderr
.write("warning: %s: %s\n" % (filename
, msg
))
69 """Convert a hex string representing an IP address to conventional
70 human-readable form, ie. dotted-quad decimal for IPv4, and
71 8 colon-separated hex shorts for IPv6.
74 # IPv4, eg. "7f000001" -> "127.0.0.1"
75 return "%d.%d.%d.%d" % tuple(pack(">L", int(ip
, 16)))
77 # IPv6 is actually simpler -- it's just a string-slicing operation,
78 # eg. "00000000000000000000ffff7f000001" ->
79 # "0000:0000:0000:0000:0000:ffff:7f00:0001"
80 return ":".join([ip
[(4*i
) : (4*i
+4)] for i
in range(8)])
84 return convert_ip(match
.group(1))
87 return ":" + str(int(match
.group(1), 16))
89 def decode_client(words
, i
):
90 chunks
= words
[i
].split(":")
91 if len(chunks
) == 2: # ip:port
92 words
[i
] = "%s:%d" % (convert_ip(chunks
[0]), int(chunks
[1], 16))
93 elif len(chunks
) == 3:
94 words
[i
] = "%s:%d (id %d)" % (convert_ip(chunks
[0]),
98 def decode_ip(words
, i
):
99 words
[i
] = convert_ip(words
[i
])
101 def decode_ttl(words
, i
):
102 words
[i
] = "TTL=%s" % words
[i
]
104 def decode_serial(words
, i
):
105 serial
= int(words
[i
])
106 words
[i
] = "#%d" % serial
108 def decode_type(words
, i
):
110 words
[i
] = query_type
.get(int(qt
), qt
)
112 def handle_dnscache_log(line
, match
):
113 (timestamp
, event
, data
) = match
.groups()
116 if event
== "cached":
117 if words
[0] not in ("cname", "ns", "nxdomain"):
118 decode_type(words
, 0)
120 elif event
== "drop":
121 decode_serial(words
, 0)
123 elif event
== "lame":
126 elif event
== "nodata":
129 decode_type(words
, 2)
131 elif event
== "nxdomain":
135 elif event
== "query":
136 decode_serial(words
, 0)
137 decode_client(words
, 1)
138 decode_type(words
, 2)
143 if words
[2] not in ("cname", "mx", "ns", "ptr", "soa"):
144 decode_type(words
, 2)
145 if words
[2] == "a": # decode answer to an A query
147 if words
[2] == "txt": # text record
149 if response
.endswith("..."):
151 response
= response
[0:-3]
154 length
= int(response
[0:2], 16)
156 for i
in range(1, len(response
)/2):
157 chars
.append(chr(int(response
[2*i
: (2*i
)+2], 16)))
158 words
[4] = "%d:\"%s%s\"" % (length
, "".join(chars
), ellipsis
)
160 elif event
== "sent":
161 decode_serial(words
, 0)
163 elif event
== "stats":
164 words
[0] = "count=%s" % words
[0]
165 words
[1] = "motion=%s" % words
[1]
166 words
[2] = "udp-active=%s" % words
[2]
167 words
[3] = "tcp-active=%s" % words
[3]
170 words
[0] = "g=%s" % words
[0]
171 decode_type(words
, 1)
173 # words[3] = control (domain for which these servers are believed
174 # to be authoritative)
175 for i
in range(4, len(words
)):
178 elif event
in ("tcpopen", "tcpclose"):
179 decode_client(words
, 0)
181 print(timestamp
, event
, " ".join(words
))
184 def handle_tinydns_log(line
, match
):
185 (timestamp
, ip
, port
, id, code
, type, name
) = match
.groups()
189 type = int(type, 16) # "001c" -> 28
190 type = query_type
.get(type, type) # 28 -> "aaaa"
195 print ("sent response to %s:%s (id %s): %s %s"
196 % (ip
, port
, id, type, name
))
197 elif code
in ("-", "I", "C"):
198 reason
= query_drop_reason
[code
]
199 print ("dropped query (%s) from %s:%s (id %s): %s %s"
200 % (reason
, ip
, port
, id, type, name
))
202 print ("dropped query (couldn't parse) from %s:%s"
205 print ("%s from %s:%s (id %s): %s %s"
206 % (code
, ip
, port
, id, type, name
))
209 def parse_logfile(file, filename
):
210 # Open pipe to tai64nlocal: we will write lines of our input (the
211 # raw log file) to it, and read log lines with readable timestamps
213 tai
= Popen(["tai64nlocal"], stdin
=PIPE
, stdout
=PIPE
, text
=True, bufsize
=0)
216 tai
.stdin
.write(line
)
217 line
= tai
.stdout
.readline()
219 match
= tinydns_log_re
.match(line
)
221 handle_tinydns_log(line
, match
)
224 match
= dnscache_log_re
.match(line
)
226 handle_dnscache_log(line
, match
)
229 sys
.stdout
.write(line
)
232 if len(sys
.argv
) > 1:
233 for filename
in sys
.argv
[1:]:
235 parse_logfile(sys
.stdin
, "(stdin)")
237 with open(filename
) as file:
238 parse_logfile(file, filename
)
240 parse_logfile(sys
.stdin
, "(stdin)")
244 if __name__
== "__main__":