Varnishlog Parser is a small Go library built to parse and analyze
varnishlogoutput, just like the name suggests.
A frontend to easily parse the logs is implemented using this library.
An instance is available here: varnishlog.iou.re
go get github.com/aorith/varnishlog-parserReference documentation is available on pkg.go.dev.
package main
import (
"fmt"
"strings"
"github.com/aorith/varnishlog-parser/assets"
"github.com/aorith/varnishlog-parser/vsl"
)
func main() {
p := vsl.NewTransactionParser(strings.NewReader(assets.VCLCached)) // Replace with your own varnishlog log
txsSet, err := p.Parse()
if err != nil {
fmt.Println(err)
return
}
// Iterate all the transactions and their VSL log records
for _, tx := range txsSet.Transactions() {
fmt.Printf("%v\n", tx.TXID)
for _, r := range tx.Records {
switch record := r.(type) {
case vsl.TimestampRecord:
fmt.Printf(" [%s] %s: %s\n", record.GetTag(), record.EventLabel, record.SinceLast.String())
case vsl.SessCloseRecord:
fmt.Printf(" [%s] %s: %s\n", record.GetTag(), record.Reason, record.Duration.String())
default:
fmt.Printf(" [%s] %s\n", record.GetTag(), record.GetRawValue())
}
}
}
}Output:
6-sess
[Begin] sess 0 HTTP/1
[SessOpen] 192.168.65.1 61200 http 192.168.50.10 80 1762612268.273411 28
[Link] req 9 rxreq
[SessClose] REM_CLOSE: 111ms
[End]
9-req-rxreq
[Begin] req 6 rxreq
[Timestamp] Start: 0s
[Timestamp] Req: 0s
[VCL_use] boot
[ReqStart] 192.168.65.1 61200 http
[ReqMethod] GET
[ReqURL] /item
[ReqProtocol] HTTP/1.1
[ . . . ]
[RespHeader] X-Varnish: 9 8
[RespHeader] Age: 0
[RespHeader] Via: 1.1 4dab8a10025c (Varnish/7.7)
[RespHeader] Accept-Ranges: bytes
[VCL_call] DELIVER
[VCL_return] deliver
[Timestamp] Process: 100µs
[Filters]
[RespHeader] Connection: keep-alive
[Timestamp] Resp: 126µs
[ReqAcct] 121 0 121 251 304 555
[End]
Parsing always starts with vsl.NewTransactionParser(r io.Reader).Parse(), which
returns a vsl.TransactionSet: a collection of every Begin/End transaction
found in the log (sessions, client requests and backend requests), linked
together by their parent/child relationships.
vsl.TransactionSet:
Transactions() []*Transaction- every parsed transaction, sorted by VXID.GetTX(vxid VXID) *Transaction/GetChildTX(parent, child VXID) *Transaction- direct lookups.UniqueRootParents(includeSession bool) []*Transaction- only the top-level transactions, e.g. one entry per logical client request instead of every session/request/bereq/ESI sub-transaction. Usually the best starting point when iterating "requests" rather than raw transactions.RawLog() string/RawLogForTx(tx *Transaction, includeChildren bool) string- reconstruct the original VSL text for the whole set, or for a single transaction (optionally with its children).
vsl.Transaction:
RecordByTag(tag string, first bool) Record- the first or last record matching a VSL tag (see thevsl/tagspackage for tag constants), e.g.tx.RecordByTag(tags.Hit, true).RecordValueByTag(tag string, first bool) string- same, but returns the raw value directly.LastRecordByTag/NextRecordByTag- search backwards/forwards from a given index, useful while walkingtx.Recordsin order.Duration(),StartTime(),EndTime()- approximate transaction timing.ReqHeaders/RespHeaders Headers- final, post-VCL header state.Headers.Get(name string, received bool)fetches a header value; passreceived: trueto get the original value as sent by the client/backend, before any VCL rewriting.
Two packages build on top of a vsl.TransactionSet to summarize or analyze it:
vsl/summaryBandwidth(ts) BandwidthReport- byte accounting (ReqAcct/BereqAcct) split between client and backend traffic, plus a per-transaction breakdown.TimestampEventsSummary(ts) []*LatencyCounter- min/max/avg/p90/p99 latency for every VSLTimestampevent label (e.g.Fetch,Process,Resp).
vsl/diagnosticsRun(ts) []Finding- scans every transaction for common Varnish misconfigurations and errors: cache fragmentation (Vary: User-Agent,Vary: *, ...), a cachedSet-CookieorAuthorizationresponse served on a hit, long hit-for-pass/grace periods, backend/ESI/VCL errors, malformed requests, abnormal session closes, retry storms, and more. EachFindingcarries aSeverity(Info/Warning/Critical), a stableRuleid, a human-readable summary/detail, and the offending transaction'sTXID/VXID.
for _, finding := range diagnostics.Run(txsSet) {
fmt.Println(finding) // [Critical] vary-user-agent (10-req-rxreq): Vary: User-Agent fragments the cache... - Vary header: "User-Agent, Accept-Encoding"
}This is the same engine used to power the "Diagnostics" section of the web UI.
Transactions can be marshaled into JSON directly:
b, err := json.MarshalIndent(txsSet.Transactions(), "", " ")
if err != nil {
panic(err)
}
fmt.Println(string(b))Output (trimmed for brevity):
[
{
"TXID": "4-req-rxreq",
"VXID": 4,
"TXType": "Request",
"Records": [
{
"Tag": "Begin",
"RawValue": "req 1 rxreq",
"RecordType": "req",
"Parent": 1,
"Reason": "rxreq"
},
{
"Tag": "RespHeader",
"RawValue": "Via: 1.1 e088e52945df (Varnish/7.7)",
"Name": "Via",
"Value": "1.1 e088e52945df (Varnish/7.7)",
"HeaderType": "RespHeader"
}
],
"ReqHeaders": { ... },
"RespHeaders": { ... },
"Parent": 1,
"Children": null
}
]Either clone this repository and run:
go run cmd/serverOr with docker/podman:
docker run --rm -p 8080:8080 ghcr.io/aorith/varnishlog-parser:latest