mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2026-05-30 23:30:40 +03:00
Compare commits
416 Commits
weakpointe
...
pmm-6401-v
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d6cb7d09e5 | ||
|
|
61b84e9021 | ||
|
|
54df0fa870 | ||
|
|
cd513b9758 | ||
|
|
cf7eb6bc7c | ||
|
|
2404b4bc00 | ||
|
|
e3e06b1f47 | ||
|
|
1d0ad32b30 | ||
|
|
2557e66ee0 | ||
|
|
381d4494e9 | ||
|
|
b7b731d340 | ||
|
|
1016aae126 | ||
|
|
5c2f85f38d | ||
|
|
2d8f54f831 | ||
|
|
778c092740 | ||
|
|
9f8ada83b6 | ||
|
|
0b503fba0b | ||
|
|
f6c91b49a2 | ||
|
|
2faa23c495 | ||
|
|
fd49331671 | ||
|
|
4de0514731 | ||
|
|
b65a9f2057 | ||
|
|
0eb733a31e | ||
|
|
6be10fb2ff | ||
|
|
7a503e0c91 | ||
|
|
31a3672982 | ||
|
|
1590ddecba | ||
|
|
b80ebb8bfd | ||
|
|
58ecb90665 | ||
|
|
f7d0d3a229 | ||
|
|
af85055f3a | ||
|
|
ca20478a69 | ||
|
|
c8c20b7f7a | ||
|
|
35263983a6 | ||
|
|
a2c901423b | ||
|
|
382721a3ac | ||
|
|
d688f9a744 | ||
|
|
c060c6d839 | ||
|
|
927ded6c3b | ||
|
|
d4123e135f | ||
|
|
4b86a18105 | ||
|
|
c6154f8f52 | ||
|
|
b4c79fc606 | ||
|
|
b4529df08d | ||
|
|
a63fb21ab2 | ||
|
|
7a19b2a14c | ||
|
|
e06d855636 | ||
|
|
e29fe89791 | ||
|
|
978594f50f | ||
|
|
e16015fa3b | ||
|
|
8033f1705c | ||
|
|
9f1e9c54c8 | ||
|
|
d59e66caa8 | ||
|
|
a2e224593e | ||
|
|
a2d68d249b | ||
|
|
713d3431fe | ||
|
|
02642248cf | ||
|
|
1aebd15549 | ||
|
|
43f0baabcd | ||
|
|
eba0e6dbc0 | ||
|
|
f0f1eb07dc | ||
|
|
bb7b59033d | ||
|
|
e0cef082f4 | ||
|
|
20fedaf7c2 | ||
|
|
efc5190950 | ||
|
|
14ab18375f | ||
|
|
4280cc281a | ||
|
|
740638ad30 | ||
|
|
3d377d0c22 | ||
|
|
99aeb3b21b | ||
|
|
d60c212784 | ||
|
|
dc9537f44e | ||
|
|
1b9a279494 | ||
|
|
f42572e049 | ||
|
|
827cde4c64 | ||
|
|
7c271d6a39 | ||
|
|
b61e9297a1 | ||
|
|
88b4c30021 | ||
|
|
ab535bf127 | ||
|
|
fee8a30f1a | ||
|
|
02ffbfb8dc | ||
|
|
3822d83276 | ||
|
|
8561bb48fd | ||
|
|
a32a9070c1 | ||
|
|
b596228765 | ||
|
|
d0f9a5d4c4 | ||
|
|
472a9360e6 | ||
|
|
b00fcad604 | ||
|
|
3d755041c3 | ||
|
|
e22a9d6ba6 | ||
|
|
9d7dc73038 | ||
|
|
63d9048990 | ||
|
|
8db1fd2f78 | ||
|
|
8f0afc656e | ||
|
|
be94882ada | ||
|
|
ff990ab0c5 | ||
|
|
5c8a01aecc | ||
|
|
2ce4d04d8e | ||
|
|
b026ebe91e | ||
|
|
c2b724d3ab | ||
|
|
e4a61581e1 | ||
|
|
a38bf70679 | ||
|
|
7b41c9ac72 | ||
|
|
c1d42f3288 | ||
|
|
4167344edb | ||
|
|
44e388ee6a | ||
|
|
b8ab0b2f31 | ||
|
|
dcc4b84319 | ||
|
|
37f48cdaa5 | ||
|
|
a39140baef | ||
|
|
30c0a37032 | ||
|
|
32e46ea35f | ||
|
|
6faaefef7b | ||
|
|
5cd89aaaa1 | ||
|
|
3a21fde0f3 | ||
|
|
274627943e | ||
|
|
21140318cc | ||
|
|
3f5bc2adce | ||
|
|
a5975c31c2 | ||
|
|
fad61eafc1 | ||
|
|
30453af768 | ||
|
|
7737321133 | ||
|
|
a2ab1f0ec9 | ||
|
|
a092df3f84 | ||
|
|
c3f178aa53 | ||
|
|
393e7636be | ||
|
|
ebc200846c | ||
|
|
0158237875 | ||
|
|
be5bbb7ba7 | ||
|
|
b79f02de21 | ||
|
|
ac58ab9664 | ||
|
|
0613ac5d02 | ||
|
|
22e48e6517 | ||
|
|
1f0432b5c1 | ||
|
|
079953b4ea | ||
|
|
d92da32041 | ||
|
|
8548650c2d | ||
|
|
2dd82e8355 | ||
|
|
bf0b5602d0 | ||
|
|
e25d05f992 | ||
|
|
5ce8fa8b10 | ||
|
|
881f22ca62 | ||
|
|
38294e2f17 | ||
|
|
2d909f4979 | ||
|
|
0821298471 | ||
|
|
fa5cda60d9 | ||
|
|
700eb5bb1d | ||
|
|
70bcc97d1c | ||
|
|
0074539441 | ||
|
|
fe0ab3840f | ||
|
|
c4fc87f8b8 | ||
|
|
8e3198ba29 | ||
|
|
6c7c0790a0 | ||
|
|
33343695a9 | ||
|
|
db553f12bc | ||
|
|
07fe2c5361 | ||
|
|
22e87b0088 | ||
|
|
f105e2e8c3 | ||
|
|
20414b3038 | ||
|
|
fcb7ef68f8 | ||
|
|
626142ab90 | ||
|
|
fd1b8be2e5 | ||
|
|
d39ba2536e | ||
|
|
e2c4578751 | ||
|
|
6ad7b0619c | ||
|
|
3a15bc761b | ||
|
|
bd79706eb3 | ||
|
|
e69fb9f3cf | ||
|
|
1a9cb85647 | ||
|
|
a80f0c9f42 | ||
|
|
4db1d24973 | ||
|
|
1c9f5b3580 | ||
|
|
9682c23786 | ||
|
|
bd2bb272f0 | ||
|
|
6111abd0e6 | ||
|
|
3f3f664b76 | ||
|
|
d1c6fb74fc | ||
|
|
b9668d5294 | ||
|
|
96160000e0 | ||
|
|
28e961e511 | ||
|
|
628e87e727 | ||
|
|
3600c97ad7 | ||
|
|
bb154f8829 | ||
|
|
d2e293b5c9 | ||
|
|
e80ddbebd4 | ||
|
|
bdd4940140 | ||
|
|
a8fee2d9b6 | ||
|
|
2dbbf51ea9 | ||
|
|
cd5cc4ec81 | ||
|
|
549d430907 | ||
|
|
69aef55ae7 | ||
|
|
274145af2d | ||
|
|
c444f7e2b9 | ||
|
|
10f41ea5f9 | ||
|
|
46f803fa7a | ||
|
|
ffe9bd248c | ||
|
|
151286f5a8 | ||
|
|
77a1af4f7f | ||
|
|
c83ff99e0d | ||
|
|
4a0c9a1069 | ||
|
|
2fd56ddb38 | ||
|
|
b42e5627fb | ||
|
|
57375e72fa | ||
|
|
0746766d95 | ||
|
|
6712a8269c | ||
|
|
4e20ea4b59 | ||
|
|
44dfb2ec0d | ||
|
|
e7b4e657a1 | ||
|
|
cd91c29243 | ||
|
|
8b8e547dc8 | ||
|
|
34a6b1fa3b | ||
|
|
af37ec8020 | ||
|
|
fff8ff946f | ||
|
|
fdccca238a | ||
|
|
1b24afec36 | ||
|
|
cacd3d6f6d | ||
|
|
8632b8200e | ||
|
|
0445ad59db | ||
|
|
f7b52b64a3 | ||
|
|
7fc62feddc | ||
|
|
0ea0168d98 | ||
|
|
3dec16702a | ||
|
|
993ecbb141 | ||
|
|
35eb512efa | ||
|
|
7f01217c3c | ||
|
|
2398b4a10a | ||
|
|
5a60387eea | ||
|
|
2685992ca9 | ||
|
|
ee63748753 | ||
|
|
620b0d11b7 | ||
|
|
316cac2c0b | ||
|
|
9eb61e67af | ||
|
|
a7333a7380 | ||
|
|
ee5bd20157 | ||
|
|
d713bdec20 | ||
|
|
6a5d6244d4 | ||
|
|
095feeee41 | ||
|
|
9dd493363c | ||
|
|
d964b04efd | ||
|
|
ec01a188fd | ||
|
|
40112df441 | ||
|
|
9e74fe3145 | ||
|
|
2c22e168f5 | ||
|
|
5747b78f6f | ||
|
|
d9166e899e | ||
|
|
38699170c9 | ||
|
|
5b4f7bbc0c | ||
|
|
db85f4a1cb | ||
|
|
780b2a139a | ||
|
|
9d2805320b | ||
|
|
e636cab272 | ||
|
|
90a1502335 | ||
|
|
f8a05d4ada | ||
|
|
ae64c2db61 | ||
|
|
37a4347a37 | ||
|
|
20cdb879e7 | ||
|
|
7917486d78 | ||
|
|
107607bf47 | ||
|
|
78b028064f | ||
|
|
db286fdd73 | ||
|
|
e8ff658b2e | ||
|
|
e1668e7441 | ||
|
|
0d0469cc80 | ||
|
|
8d6d4e8033 | ||
|
|
b894f25f21 | ||
|
|
b6bae2f05f | ||
|
|
9e15858baf | ||
|
|
3f5b1084eb | ||
|
|
c2e9be96a7 | ||
|
|
a72dadb8f4 | ||
|
|
08219faf8d | ||
|
|
288620ca40 | ||
|
|
2847c84a7b | ||
|
|
6a64823581 | ||
|
|
b94e986710 | ||
|
|
a29565d1bd | ||
|
|
39332cfc5c | ||
|
|
d07d2811d4 | ||
|
|
206e451cae | ||
|
|
307034fc2f | ||
|
|
c149132b14 | ||
|
|
6dd7a90c7c | ||
|
|
dc5507754f | ||
|
|
c68663deee | ||
|
|
114a40e63f | ||
|
|
163f2a46fd | ||
|
|
375c46cb1f | ||
|
|
bb2d1128b8 | ||
|
|
479b9da827 | ||
|
|
62857fc30e | ||
|
|
253315b1fe | ||
|
|
efe6e30008 | ||
|
|
bc2512abdd | ||
|
|
a07f8017ba | ||
|
|
cf70b766eb | ||
|
|
b00732074c | ||
|
|
8df8c414de | ||
|
|
ce844238a4 | ||
|
|
452720c5dc | ||
|
|
bbca1740c1 | ||
|
|
e1c85395eb | ||
|
|
b348114dab | ||
|
|
bb54e34dc5 | ||
|
|
e0d0b9447e | ||
|
|
fae6e4fc85 | ||
|
|
e49bf9bc73 | ||
|
|
a142390014 | ||
|
|
bceb8082f6 | ||
|
|
276969500e | ||
|
|
030e3a63f2 | ||
|
|
1c5e0564af | ||
|
|
b8300338f0 | ||
|
|
660c3c7251 | ||
|
|
80ba07dc95 | ||
|
|
11ded82e60 | ||
|
|
558b390ebc | ||
|
|
343f444e87 | ||
|
|
16884c20c0 | ||
|
|
7d44cdd8ce | ||
|
|
5d2394ad9b | ||
|
|
8582fba4b1 | ||
|
|
b045f506f2 | ||
|
|
6197440bb9 | ||
|
|
966e9c227a | ||
|
|
edb2ab7d8e | ||
|
|
0ad887fd4d | ||
|
|
d5dde7f6b1 | ||
|
|
a54ca9bd8f | ||
|
|
3588687f84 | ||
|
|
687eb4ab00 | ||
|
|
b04fece006 | ||
|
|
d0c364d93d | ||
|
|
63c88d8ea2 | ||
|
|
dc6636e2b2 | ||
|
|
c13f1d99e0 | ||
|
|
079888f719 | ||
|
|
b68264b4f5 | ||
|
|
aed049f660 | ||
|
|
7fcc0a1ef0 | ||
|
|
48951073c4 | ||
|
|
d0dfcb72b4 | ||
|
|
4cf7a55808 | ||
|
|
d72fc60108 | ||
|
|
0b92e18047 | ||
|
|
aa8ea16160 | ||
|
|
f5e70f0ab9 | ||
|
|
9e10d5083e | ||
|
|
30c2d75815 | ||
|
|
0e80f3f45a | ||
|
|
6e3cbae0b3 | ||
|
|
a5583ddaff | ||
|
|
5db9e82e54 | ||
|
|
80676cf1fd | ||
|
|
ba4c49dde6 | ||
|
|
35e5e8ff1e | ||
|
|
4cdbc4642d | ||
|
|
23c0fb1efc | ||
|
|
441d3e4b3f | ||
|
|
a0ea5777f0 | ||
|
|
fb006fc6c0 | ||
|
|
8593358965 | ||
|
|
d0311b7fe5 | ||
|
|
4edd38a906 | ||
|
|
56054f4eb7 | ||
|
|
0ff0787797 | ||
|
|
f9c706e186 | ||
|
|
d74d22460c | ||
|
|
d1193c87a8 | ||
|
|
4f311e5827 | ||
|
|
142e6b6ecf | ||
|
|
1b4ef473b9 | ||
|
|
8beb1f9519 | ||
|
|
501fd8efd9 | ||
|
|
45f2ba2572 | ||
|
|
cb2342029e | ||
|
|
ff0088ceec | ||
|
|
afe6d2e736 | ||
|
|
e1a6262302 | ||
|
|
f000a10cd0 | ||
|
|
4aee6ef4c0 | ||
|
|
f4dfacd493 | ||
|
|
fb2d4e56ce | ||
|
|
36b748dfc7 | ||
|
|
c625dc5b96 | ||
|
|
e32620afa1 | ||
|
|
3f298272a8 | ||
|
|
7a473798b7 | ||
|
|
00ce906d97 | ||
|
|
41c9565aa1 | ||
|
|
56303aee5b | ||
|
|
8d8e2ccf5f | ||
|
|
8772cb617c | ||
|
|
65fbfc5cbc | ||
|
|
1b389674c0 | ||
|
|
98529e16ee | ||
|
|
1b112405a8 | ||
|
|
8bbc83e85e | ||
|
|
8349140744 | ||
|
|
4dc13754d8 | ||
|
|
83b7eb8ca6 | ||
|
|
e5ef3288dd | ||
|
|
e7f2907138 | ||
|
|
757c5cfbe0 | ||
|
|
317ddb84b9 | ||
|
|
2b1d0510fa | ||
|
|
40d2f6fee4 | ||
|
|
9fbb84d5c2 | ||
|
|
bdaa9a91f3 | ||
|
|
1a91da35be | ||
|
|
f85be226bb | ||
|
|
8df5a3c5f6 | ||
|
|
9d3eb3f4b8 | ||
|
|
2cd48959d4 | ||
|
|
8fc8874db4 | ||
|
|
ff1cbb524e | ||
|
|
a70df4bd83 |
@@ -39,8 +39,16 @@ var (
|
||||
"The saved data survives unclean shutdowns such as OOM crash, hardware reset, SIGKILL, etc. "+
|
||||
"Bigger intervals may help increase the lifetime of flash storage with limited write cycles (e.g. Raspberry PI). "+
|
||||
"Smaller intervals increase disk IO load. Minimum supported value is 1s")
|
||||
downsamplingPeriods = flagutil.NewArrayString("downsampling.period", "Comma-separated downsampling periods in the format 'offset:period'. For example, '30d:10m' instructs "+
|
||||
"to leave a single sample per 10 minutes for samples older than 30 days. See https://docs.victoriametrics.com/#downsampling for details")
|
||||
)
|
||||
|
||||
// custom api help links [["/api","doc"]] without http.pathPrefix.
|
||||
var customAPIPathList = [][]string{
|
||||
{"/graph/explore", "explore metrics grafana page"},
|
||||
{"/graph/d/prometheus-advanced/advanced-data-exploration", "PMM grafana dashboard"},
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Write flags and help message to stdout, since it is easier to grep or pipe.
|
||||
flag.CommandLine.SetOutput(os.Stdout)
|
||||
@@ -72,7 +80,10 @@ func main() {
|
||||
}
|
||||
logger.Infof("starting VictoriaMetrics at %q...", listenAddrs)
|
||||
startTime := time.Now()
|
||||
storage.SetDedupInterval(*minScrapeInterval)
|
||||
err := storage.SetDownsamplingPeriods(*downsamplingPeriods, *minScrapeInterval)
|
||||
if err != nil {
|
||||
logger.Fatalf("cannot parse -downsampling.period: %s", err)
|
||||
}
|
||||
storage.SetDataFlushInterval(*inmemoryDataFlushInterval)
|
||||
vmstorage.Init(promql.ResetRollupResultCacheIfNeeded)
|
||||
vmselect.Init()
|
||||
@@ -130,6 +141,10 @@ func requestHandler(w http.ResponseWriter, r *http.Request) bool {
|
||||
{"api/v1/status/active_queries", "active queries"},
|
||||
{"-/reload", "reload configuration"},
|
||||
})
|
||||
for _, p := range customAPIPathList {
|
||||
p, doc := p[0], p[1]
|
||||
fmt.Fprintf(w, "<a href=%q>%s</a> - %s<br/>", p, p, doc)
|
||||
}
|
||||
return true
|
||||
}
|
||||
if vminsert.RequestHandler(w, r) {
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmselect/searchutils"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmstorage"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmstorage/promdb"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/cgroup"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fasttime"
|
||||
@@ -337,6 +338,12 @@ var (
|
||||
type packedTimeseries struct {
|
||||
metricName string
|
||||
brs []blockRef
|
||||
pd *promData
|
||||
}
|
||||
|
||||
type promData struct {
|
||||
values []float64
|
||||
timestamps []int64
|
||||
}
|
||||
|
||||
type unpackWork struct {
|
||||
@@ -440,9 +447,21 @@ func (pts *packedTimeseries) Unpack(dst *Result, tbf *tmpBlocksFile, tr storage.
|
||||
putSortBlocksHeap(sbh)
|
||||
return err
|
||||
}
|
||||
dedupInterval := storage.GetDedupInterval()
|
||||
if pts.pd != nil {
|
||||
// Add data from Prometheus to dst.
|
||||
// It usually has smaller timestamps than the data from sbs, so put it first.
|
||||
dst.Values = append(dst.Values, pts.pd.values...)
|
||||
dst.Timestamps = append(dst.Timestamps, pts.pd.timestamps...)
|
||||
}
|
||||
dedupInterval := storage.GetDedupInterval(tr.MinTimestamp)
|
||||
mergeSortBlocks(dst, sbh, dedupInterval)
|
||||
putSortBlocksHeap(sbh)
|
||||
if pts.pd != nil {
|
||||
if !sort.IsSorted(dst) {
|
||||
sort.Sort(dst)
|
||||
}
|
||||
pts.pd = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -559,6 +578,27 @@ func (pts *packedTimeseries) unpackTo(dst []*sortBlock, tbf *tmpBlocksFile, tr s
|
||||
return dst, firstErr
|
||||
}
|
||||
|
||||
// sort.Interface implementation for Result
|
||||
|
||||
// Len implements sort.Interface
|
||||
func (r *Result) Len() int {
|
||||
return len(r.Timestamps)
|
||||
}
|
||||
|
||||
// Less implements sort.Interface
|
||||
func (r *Result) Less(i, j int) bool {
|
||||
timestamps := r.Timestamps
|
||||
return timestamps[i] < timestamps[j]
|
||||
}
|
||||
|
||||
// Swap implements sort.Interface
|
||||
func (r *Result) Swap(i, j int) {
|
||||
timestamps := r.Timestamps
|
||||
values := r.Values
|
||||
timestamps[i], timestamps[j] = timestamps[j], timestamps[i]
|
||||
values[i], values[j] = values[j], values[i]
|
||||
}
|
||||
|
||||
func getSortBlock() *sortBlock {
|
||||
v := sbPool.Get()
|
||||
if v == nil {
|
||||
@@ -796,6 +836,15 @@ func LabelNames(qt *querytracer.Tracer, sq *storage.SearchQuery, maxLabelNames i
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error during labels search on time range: %w", err)
|
||||
}
|
||||
|
||||
// Merge labels obtained from Prometheus storage.
|
||||
promLabels, err := promdb.GetLabelNamesOnTimeRange(tr, deadline)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot obtain labels from Prometheus storage: %w", err)
|
||||
}
|
||||
qt.Printf("get %d label names from Prometheus storage", len(promLabels))
|
||||
labels = mergeStrings(labels, promLabels)
|
||||
|
||||
// Sort labels like Prometheus does
|
||||
sort.Strings(labels)
|
||||
qt.Printf("sort %d labels", len(labels))
|
||||
@@ -867,14 +916,44 @@ func LabelValues(qt *querytracer.Tracer, labelName string, sq *storage.SearchQue
|
||||
}
|
||||
labelValues, err := vmstorage.SearchLabelValuesWithFiltersOnTimeRange(qt, labelName, tfss, tr, maxLabelValues, sq.MaxMetrics, deadline.Deadline())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error during label values search on time range for labelName=%q: %w", labelName, err)
|
||||
return nil, fmt.Errorf("error during label values search on time range: %w", err)
|
||||
}
|
||||
|
||||
// Merge label values obtained from Prometheus storage.
|
||||
promLabelValues, err := promdb.GetLabelValuesOnTimeRange(labelName, tr, deadline)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot obtain label values on time range for %q from Prometheus storage: %w", labelName, err)
|
||||
}
|
||||
qt.Printf("get %d label values from Prometheus storage", len(promLabelValues))
|
||||
labelValues = mergeStrings(labelValues, promLabelValues)
|
||||
|
||||
// Sort labelValues like Prometheus does
|
||||
sort.Strings(labelValues)
|
||||
qt.Printf("sort %d label values", len(labelValues))
|
||||
return labelValues, nil
|
||||
}
|
||||
|
||||
func mergeStrings(a, b []string) []string {
|
||||
if len(a) == 0 {
|
||||
return b
|
||||
}
|
||||
if len(b) == 0 {
|
||||
return a
|
||||
}
|
||||
m := make(map[string]struct{}, len(a)+len(b))
|
||||
for _, s := range a {
|
||||
m[s] = struct{}{}
|
||||
}
|
||||
for _, s := range b {
|
||||
m[s] = struct{}{}
|
||||
}
|
||||
result := make([]string, 0, len(m))
|
||||
for s := range m {
|
||||
result = append(result, s)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// GraphiteTagValues returns tag values for the given tagName until the given deadline.
|
||||
func GraphiteTagValues(qt *querytracer.Tracer, tagName, filter string, limit int, deadline searchutils.Deadline) ([]string, error) {
|
||||
qt = qt.NewChild("get graphite tag values for tagName=%s, filter=%s, limit=%d", tagName, filter, limit)
|
||||
@@ -1280,6 +1359,26 @@ func ProcessSearchQuery(qt *querytracer.Tracer, sq *storage.SearchQuery, deadlin
|
||||
}
|
||||
qt.Printf("fetch unique series=%d, blocks=%d, samples=%d, bytes=%d", len(m), blocksRead, samples, tbf.Len())
|
||||
|
||||
// Fetch data from promdb.
|
||||
pm := make(map[string]*promData)
|
||||
err = promdb.VisitSeries(sq, deadline, func(metricName []byte, values []float64, timestamps []int64) {
|
||||
pd := pm[string(metricName)]
|
||||
if pd == nil {
|
||||
if _, ok := m[string(metricName)]; !ok {
|
||||
orderedMetricNames = append(orderedMetricNames, string(metricName))
|
||||
}
|
||||
pd = &promData{}
|
||||
pm[string(metricName)] = pd
|
||||
}
|
||||
pd.values = append(pd.values, values...)
|
||||
pd.timestamps = append(pd.timestamps, timestamps...)
|
||||
})
|
||||
if err != nil {
|
||||
putTmpBlocksFile(tbf)
|
||||
putStorageSearch(sr)
|
||||
return nil, fmt.Errorf("error when searching in Prometheus data: %w", err)
|
||||
}
|
||||
|
||||
var rss Results
|
||||
rss.tr = tr
|
||||
rss.deadline = deadline
|
||||
@@ -1288,6 +1387,7 @@ func ProcessSearchQuery(qt *querytracer.Tracer, sq *storage.SearchQuery, deadlin
|
||||
pts[i] = packedTimeseries{
|
||||
metricName: metricName,
|
||||
brs: brssPool[m[metricName]].brs,
|
||||
pd: pm[metricName],
|
||||
}
|
||||
}
|
||||
rss.packedTimeseries = pts
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
|
||||
"github.com/VictoriaMetrics/metrics"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmstorage/promdb"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/encoding"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fasttime"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/flagutil"
|
||||
@@ -124,9 +125,11 @@ func Init(resetCacheIfNeeded func(mrs []storage.MetricRow)) {
|
||||
// register storage metrics
|
||||
storageMetrics = metrics.NewSet()
|
||||
storageMetrics.RegisterMetricsWriter(func(w io.Writer) {
|
||||
writeStorageMetrics(w, strg)
|
||||
writeStorageMetrics(w, Storage)
|
||||
})
|
||||
metrics.RegisterSet(storageMetrics)
|
||||
|
||||
promdb.Init(retentionPeriod.Milliseconds())
|
||||
}
|
||||
|
||||
var storageMetrics *metrics.Set
|
||||
@@ -247,6 +250,7 @@ func Stop() {
|
||||
logger.Infof("gracefully closing the storage at %s", *DataPath)
|
||||
startTime := time.Now()
|
||||
WG.WaitAndBlock()
|
||||
promdb.MustClose()
|
||||
stopStaleSnapshotsRemover()
|
||||
Storage.MustClose()
|
||||
logger.Infof("successfully closed the storage in %.3f seconds", time.Since(startTime).Seconds())
|
||||
|
||||
267
app/vmstorage/promdb/promdb.go
Normal file
267
app/vmstorage/promdb/promdb.go
Normal file
@@ -0,0 +1,267 @@
|
||||
package promdb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmselect/searchutils"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/storage"
|
||||
"github.com/oklog/ulid"
|
||||
"github.com/prometheus/prometheus/model/labels"
|
||||
promstorage "github.com/prometheus/prometheus/storage"
|
||||
"github.com/prometheus/prometheus/tsdb"
|
||||
"github.com/prometheus/prometheus/tsdb/chunkenc"
|
||||
"log/slog"
|
||||
)
|
||||
|
||||
var prometheusDataPath = flag.String("prometheusDataPath", "", "Optional path to readonly historical Prometheus data")
|
||||
|
||||
var prometheusRetentionMsecs int64
|
||||
|
||||
// Init must be called after flag.Parse and before using the package.
|
||||
//
|
||||
// See also MustClose.
|
||||
func Init(retentionMsecs int64) {
|
||||
if promDB != nil {
|
||||
logger.Fatalf("BUG: promdb.Init is called multiple times without promdb.MustClose call")
|
||||
}
|
||||
prometheusRetentionMsecs = retentionMsecs
|
||||
if *prometheusDataPath == "" {
|
||||
return
|
||||
}
|
||||
l := slog.New(slog.Default().Handler())
|
||||
opts := tsdb.DefaultOptions()
|
||||
opts.RetentionDuration = retentionMsecs
|
||||
|
||||
// Set max block duration to 10% of retention period or 31 days
|
||||
// according to https://prometheus.io/docs/prometheus/latest/storage/#compaction
|
||||
maxBlockDuration := int64((31 * 24 * time.Hour) / time.Millisecond)
|
||||
if maxBlockDuration > retentionMsecs/10 {
|
||||
maxBlockDuration = retentionMsecs / 10
|
||||
}
|
||||
if maxBlockDuration < opts.MinBlockDuration {
|
||||
maxBlockDuration = opts.MinBlockDuration
|
||||
}
|
||||
opts.MaxBlockDuration = maxBlockDuration
|
||||
|
||||
// Custom delete function is needed, because Prometheus by default doesn't delete
|
||||
// blocks outside the retention if no new blocks are created with samples with the current timestamps.
|
||||
// See https://github.com/prometheus/prometheus/blob/997bb7134fcfd7279f250e183e78681e48a56aff/tsdb/db.go#L1116
|
||||
opts.BlocksToDelete = func(blocks []*tsdb.Block) map[ulid.ULID]struct{} {
|
||||
m := make(map[ulid.ULID]struct{})
|
||||
minRetentionTime := time.Now().Unix()*1000 - retentionMsecs
|
||||
for _, block := range blocks {
|
||||
meta := block.Meta()
|
||||
// delete block marked for deletion by compaction code.
|
||||
if meta.Compaction.Deletable {
|
||||
m[meta.ULID] = struct{}{}
|
||||
continue
|
||||
}
|
||||
if block.MaxTime() < minRetentionTime {
|
||||
m[meta.ULID] = struct{}{}
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
pdb, err := tsdb.Open(*prometheusDataPath, l, nil, opts, nil)
|
||||
if err != nil {
|
||||
logger.Panicf("FATAL: cannot open Prometheus data at -prometheusDataPath=%q: %s", *prometheusDataPath, err)
|
||||
}
|
||||
promDB = pdb
|
||||
logger.Infof("successfully opened historical Prometheus data at -prometheusDataPath=%q with retentionMsecs=%d", *prometheusDataPath, retentionMsecs)
|
||||
}
|
||||
|
||||
// MustClose must be called on graceful shutdown.
|
||||
//
|
||||
// Package functionality cannot be used after this call.
|
||||
func MustClose() {
|
||||
if *prometheusDataPath == "" {
|
||||
return
|
||||
}
|
||||
if promDB == nil {
|
||||
logger.Panicf("BUG: promdb.MustClose is called without promdb.Init call")
|
||||
}
|
||||
if err := promDB.Close(); err != nil {
|
||||
logger.Panicf("FATAL: cannot close promDB: %s", err)
|
||||
}
|
||||
promDB = nil
|
||||
logger.Infof("successfully closed historical Prometheus data at -prometheusDataPath=%q", *prometheusDataPath)
|
||||
}
|
||||
|
||||
var promDB *tsdb.DB
|
||||
|
||||
// GetLabelNamesOnTimeRange returns label names.
|
||||
func GetLabelNamesOnTimeRange(tr storage.TimeRange, deadline searchutils.Deadline) ([]string, error) {
|
||||
if *prometheusDataPath == "" {
|
||||
return nil, nil
|
||||
}
|
||||
d := time.Unix(int64(deadline.Deadline()), 0)
|
||||
ctx, cancel := context.WithDeadline(context.Background(), d)
|
||||
defer cancel()
|
||||
q, err := promDB.Querier(tr.MinTimestamp, tr.MaxTimestamp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer mustCloseQuerier(q)
|
||||
|
||||
names, _, err := q.LabelNames(ctx, nil)
|
||||
// Make full copy of names, since they cannot be used after q is closed.
|
||||
names = copyStringsWithMemory(names)
|
||||
return names, err
|
||||
}
|
||||
|
||||
// GetLabelValuesOnTimeRange returns values for the given labelName on the given tr.
|
||||
func GetLabelValuesOnTimeRange(labelName string, tr storage.TimeRange, deadline searchutils.Deadline) ([]string, error) {
|
||||
if *prometheusDataPath == "" {
|
||||
return nil, nil
|
||||
}
|
||||
d := time.Unix(int64(deadline.Deadline()), 0)
|
||||
ctx, cancel := context.WithDeadline(context.Background(), d)
|
||||
defer cancel()
|
||||
q, err := promDB.Querier(tr.MinTimestamp, tr.MaxTimestamp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer mustCloseQuerier(q)
|
||||
|
||||
values, _, err := q.LabelValues(ctx, labelName, nil)
|
||||
// Make full copy of values, since they cannot be used after q is closed.
|
||||
values = copyStringsWithMemory(values)
|
||||
return values, err
|
||||
}
|
||||
|
||||
func copyStringsWithMemory(a []string) []string {
|
||||
result := make([]string, len(a))
|
||||
for i, s := range a {
|
||||
result[i] = string(append([]byte{}, s...))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// SeriesVisitor is called by VisitSeries for each matching time series.
|
||||
//
|
||||
// The caller shouldn't hold references to metricName, values and timestamps after returning.
|
||||
type SeriesVisitor func(metricName []byte, values []float64, timestamps []int64)
|
||||
|
||||
// VisitSeries calls f for each series found in the pdb.
|
||||
func VisitSeries(sq *storage.SearchQuery, deadline searchutils.Deadline, f SeriesVisitor) error {
|
||||
if *prometheusDataPath == "" {
|
||||
return nil
|
||||
}
|
||||
d := time.Unix(int64(deadline.Deadline()), 0)
|
||||
ctx, cancel := context.WithDeadline(context.Background(), d)
|
||||
defer cancel()
|
||||
minTime, maxTime := getSearchTimeRange(sq)
|
||||
q, err := promDB.Querier(minTime, maxTime)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer mustCloseQuerier(q)
|
||||
var seriesSet []promstorage.SeriesSet
|
||||
for _, tf := range sq.TagFilterss {
|
||||
ms, err := convertTagFiltersToMatchers(tf)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot convert tag filters to matchers: %w", err)
|
||||
}
|
||||
s := q.Select(ctx, false, nil, ms...)
|
||||
seriesSet = append(seriesSet, s)
|
||||
}
|
||||
ss := promstorage.NewMergeSeriesSet(seriesSet, promstorage.ChainedSeriesMerge)
|
||||
var (
|
||||
mn storage.MetricName
|
||||
metricName []byte
|
||||
values []float64
|
||||
timestamps []int64
|
||||
)
|
||||
var it chunkenc.Iterator
|
||||
for ss.Next() {
|
||||
s := ss.At()
|
||||
convertPromLabelsToMetricName(&mn, s.Labels())
|
||||
metricName = mn.SortAndMarshal(metricName[:0])
|
||||
values = values[:0]
|
||||
timestamps = timestamps[:0]
|
||||
it = s.Iterator(it)
|
||||
for {
|
||||
typ := it.Next()
|
||||
if typ == chunkenc.ValNone {
|
||||
break
|
||||
}
|
||||
if typ != chunkenc.ValFloat {
|
||||
// Skip unsupported values
|
||||
continue
|
||||
}
|
||||
ts, v := it.At()
|
||||
values = append(values, v)
|
||||
timestamps = append(timestamps, ts)
|
||||
}
|
||||
if err := it.Err(); err != nil {
|
||||
return fmt.Errorf("error when iterating Prometheus series: %w", err)
|
||||
}
|
||||
f(metricName, values, timestamps)
|
||||
}
|
||||
return ss.Err()
|
||||
}
|
||||
|
||||
func getSearchTimeRange(sq *storage.SearchQuery) (int64, int64) {
|
||||
maxTime := sq.MaxTimestamp
|
||||
minTime := sq.MinTimestamp
|
||||
minRetentionTime := time.Now().Unix()*1000 - prometheusRetentionMsecs
|
||||
if maxTime < minRetentionTime {
|
||||
maxTime = minRetentionTime
|
||||
}
|
||||
if minTime < minRetentionTime {
|
||||
minTime = minRetentionTime
|
||||
}
|
||||
return minTime, maxTime
|
||||
}
|
||||
|
||||
func convertPromLabelsToMetricName(dst *storage.MetricName, labels []labels.Label) {
|
||||
dst.Reset()
|
||||
for _, label := range labels {
|
||||
if label.Name == "__name__" {
|
||||
dst.MetricGroup = append(dst.MetricGroup[:0], label.Value...)
|
||||
} else {
|
||||
dst.AddTag(label.Name, label.Value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func convertTagFiltersToMatchers(tfs []storage.TagFilter) ([]*labels.Matcher, error) {
|
||||
ms := make([]*labels.Matcher, 0, len(tfs))
|
||||
for _, tf := range tfs {
|
||||
var mt labels.MatchType
|
||||
if tf.IsNegative {
|
||||
if tf.IsRegexp {
|
||||
mt = labels.MatchNotRegexp
|
||||
} else {
|
||||
mt = labels.MatchNotEqual
|
||||
}
|
||||
} else {
|
||||
if tf.IsRegexp {
|
||||
mt = labels.MatchRegexp
|
||||
} else {
|
||||
mt = labels.MatchEqual
|
||||
}
|
||||
}
|
||||
key := string(tf.Key)
|
||||
if key == "" {
|
||||
key = "__name__"
|
||||
}
|
||||
value := string(tf.Value)
|
||||
m, err := labels.NewMatcher(mt, key, value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ms = append(ms, m)
|
||||
}
|
||||
return ms, nil
|
||||
}
|
||||
|
||||
func mustCloseQuerier(q promstorage.Querier) {
|
||||
if err := q.Close(); err != nil {
|
||||
logger.Panicf("FATAL: cannot close querier: %s", err)
|
||||
}
|
||||
}
|
||||
@@ -81,7 +81,6 @@ publish-via-docker:
|
||||
--build-arg root_image=$(ROOT_IMAGE) \
|
||||
--build-arg APP_NAME=$(APP_NAME) \
|
||||
--tag $(DOCKER_NAMESPACE)/$(APP_NAME):$(PKG_TAG)$(RACE) \
|
||||
--tag $(DOCKER_NAMESPACE)/$(APP_NAME):$(LATEST_TAG)$(RACE) \
|
||||
-o type=image \
|
||||
--provenance=false \
|
||||
-f app/$(APP_NAME)/multiarch/Dockerfile \
|
||||
|
||||
@@ -18,6 +18,10 @@ See also [LTS releases](https://docs.victoriametrics.com/lts-releases/).
|
||||
|
||||
## tip
|
||||
|
||||
## [v1.107.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.107.0)
|
||||
|
||||
Released at 2023-11-29
|
||||
|
||||
**Update note 1: cmd-line flags `-datasource.lookback`, `datasource.queryTimeAlignment` and `remoteRead.ignoreRestoreErrors` on [vmalert](https://docs.victoriametrics.com/vmalert/) have been removed. Those flags were all deprecated before [v1.101.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.101.0).**
|
||||
|
||||
* FEATURE: [vmalert](https://docs.victoriametrics.com/vmalert): revert the default value of `-remoteWrite.maxQueueSize` from `1_000_000` to `100_000`. It was bumped in [v1.104.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.104.0), which increases memory usage and is not needed for most setups. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7471).
|
||||
|
||||
4
go.mod
4
go.mod
@@ -19,6 +19,7 @@ require (
|
||||
github.com/cespare/xxhash/v2 v2.3.0
|
||||
github.com/cheggaaa/pb/v3 v3.1.5
|
||||
github.com/ergochat/readline v0.1.3
|
||||
github.com/go-kit/kit v0.9.0
|
||||
github.com/gogo/protobuf v1.3.2
|
||||
github.com/golang/snappy v0.0.4
|
||||
github.com/google/go-cmp v0.6.0
|
||||
@@ -26,6 +27,7 @@ require (
|
||||
github.com/influxdata/influxdb v1.11.8
|
||||
github.com/klauspost/compress v1.17.11
|
||||
github.com/mattn/go-isatty v0.0.20
|
||||
github.com/oklog/ulid v1.3.1
|
||||
github.com/prometheus/prometheus v0.300.1
|
||||
github.com/urfave/cli/v2 v2.27.5
|
||||
github.com/valyala/fastjson v1.6.4
|
||||
@@ -83,6 +85,7 @@ require (
|
||||
github.com/envoyproxy/protoc-gen-validate v1.1.0 // indirect
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/go-logfmt/logfmt v0.5.1 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
|
||||
@@ -101,7 +104,6 @@ require (
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect
|
||||
github.com/oklog/ulid v1.3.1 // indirect
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
|
||||
4
go.sum
4
go.sum
@@ -222,11 +222,14 @@ github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.9.0 h1:wDJmvq38kDhkVxi50ni9ykkdUr1PKgqKOoi01fa0Mdk=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||
github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA=
|
||||
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
@@ -240,6 +243,7 @@ github.com/go-openapi/swag v0.22.9 h1:XX2DssF+mQKM2DHsbgZK74y/zj4mo9I99+89xUmuZC
|
||||
github.com/go-openapi/swag v0.22.9/go.mod h1:3/OXnFfnMAwBD099SwYRk7GD3xOrr1iL7d/XNLXVVwE=
|
||||
github.com/go-resty/resty/v2 v2.13.1 h1:x+LHXBI2nMB1vqndymf26quycC4aggYJ7DECYbiz03g=
|
||||
github.com/go-resty/resty/v2 v2.13.1/go.mod h1:GznXlLxkq6Nh4sU59rPmUw3VtgpO3aS96ORAI6Q7d+0=
|
||||
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-zookeeper/zk v1.0.4 h1:DPzxraQx7OrPyXq2phlGlNSIyWEsAox0RJmjTseMV6I=
|
||||
github.com/go-zookeeper/zk v1.0.4/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw=
|
||||
|
||||
@@ -163,7 +163,8 @@ func (b *Block) deduplicateSamplesDuringMerge() {
|
||||
// Nothing to dedup.
|
||||
return
|
||||
}
|
||||
dedupInterval := GetDedupInterval()
|
||||
maxTimestamp := srcTimestamps[len(srcTimestamps)-1]
|
||||
dedupInterval := GetDedupInterval(maxTimestamp)
|
||||
if dedupInterval <= 0 {
|
||||
// Deduplication is disabled.
|
||||
return
|
||||
|
||||
@@ -1,27 +1,7 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// SetDedupInterval sets the deduplication interval, which is applied to raw samples during data ingestion and querying.
|
||||
//
|
||||
// De-duplication is disabled if dedupInterval is 0.
|
||||
//
|
||||
// This function must be called before initializing the storage.
|
||||
func SetDedupInterval(dedupInterval time.Duration) {
|
||||
globalDedupInterval = dedupInterval.Milliseconds()
|
||||
}
|
||||
|
||||
// GetDedupInterval returns the dedup interval in milliseconds, which has been set via SetDedupInterval.
|
||||
func GetDedupInterval() int64 {
|
||||
return globalDedupInterval
|
||||
}
|
||||
|
||||
var globalDedupInterval int64
|
||||
|
||||
func isDedupEnabled() bool {
|
||||
return globalDedupInterval > 0
|
||||
return len(downsamplingPeriods) > 0
|
||||
}
|
||||
|
||||
// DeduplicateSamples removes samples from src* if they are closer to each other than dedupInterval in milliseconds.
|
||||
|
||||
123
lib/storage/downsampling.go
Normal file
123
lib/storage/downsampling.go
Normal file
@@ -0,0 +1,123 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fasttime"
|
||||
"github.com/VictoriaMetrics/metricsql"
|
||||
)
|
||||
|
||||
// SetDownsamplingPeriods configures downsampling.
|
||||
//
|
||||
// The function must be called before opening or creating any storage.
|
||||
func SetDownsamplingPeriods(periods []string, dedupInterval time.Duration) error {
|
||||
dsps, err := parseDownsamplingPeriods(periods)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dedupIntervalMs := dedupInterval.Milliseconds()
|
||||
if dedupIntervalMs > 0 {
|
||||
if len(dsps) > 0 && dsps[len(dsps)-1].Offset == 0 {
|
||||
return fmt.Errorf("-dedup.minScrapeInterval=%s cannot be used if -downsampling.period=%s contains zero offset", dedupInterval, periods)
|
||||
}
|
||||
// Deduplication is a special case of downsampling with zero offset.
|
||||
dsps = append(dsps, DownsamplingPeriod{
|
||||
Offset: 0,
|
||||
Interval: dedupIntervalMs,
|
||||
})
|
||||
}
|
||||
downsamplingPeriods = dsps
|
||||
return nil
|
||||
}
|
||||
|
||||
// DownsamplingPeriod describes downsampling period
|
||||
type DownsamplingPeriod struct {
|
||||
// Offset in milliseconds from the current time when the downsampling with the given interval must be applied
|
||||
Offset int64
|
||||
// Interval for downsampling - only a single sample is left per each interval
|
||||
Interval int64
|
||||
}
|
||||
|
||||
// String implements interface
|
||||
func (dsp DownsamplingPeriod) String() string {
|
||||
offset := time.Duration(dsp.Offset) * time.Millisecond
|
||||
interval := time.Duration(dsp.Interval) * time.Millisecond
|
||||
return fmt.Sprintf("%s:%s", offset, interval)
|
||||
}
|
||||
|
||||
func (dsp *DownsamplingPeriod) parse(s string) error {
|
||||
idx := strings.Index(s, ":")
|
||||
if idx <= 0 {
|
||||
return fmt.Errorf("incorrect format for downsampling period: %s, want `offset:interval` format", s)
|
||||
}
|
||||
offsetStr, intervalStr := s[:idx], s[idx+1:]
|
||||
interval, err := metricsql.DurationValue(intervalStr, 0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("incorrect interval: %s format for downsampling interval: %s err: %w", intervalStr, s, err)
|
||||
}
|
||||
offset, err := metricsql.DurationValue(offsetStr, 0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("incorrect duration: %s format for downsampling offset: %s err: %w", offsetStr, s, err)
|
||||
}
|
||||
dsp.Interval = interval
|
||||
dsp.Offset = offset
|
||||
// sanity check
|
||||
if offset > 0 && interval > offset {
|
||||
return fmt.Errorf("downsampling interval=%d cannot exceed offset=%d", dsp.Interval, dsp.Offset)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var downsamplingPeriods []DownsamplingPeriod
|
||||
|
||||
// GetDedupInterval returns dedup interval, which must be applied to samples with the given timestamp.
|
||||
func GetDedupInterval(timestamp int64) int64 {
|
||||
dsp := getDownsamplingPeriod(timestamp)
|
||||
return dsp.Interval
|
||||
}
|
||||
|
||||
// getDownsamplingPeriod returns downsampling period, which must be used for the given timestamp
|
||||
func getDownsamplingPeriod(timestamp int64) DownsamplingPeriod {
|
||||
offset := int64(fasttime.UnixTimestamp())*1000 - timestamp
|
||||
for _, dsp := range downsamplingPeriods {
|
||||
if offset >= dsp.Offset {
|
||||
return dsp
|
||||
}
|
||||
}
|
||||
return DownsamplingPeriod{}
|
||||
}
|
||||
|
||||
func parseDownsamplingPeriods(periods []string) ([]DownsamplingPeriod, error) {
|
||||
if len(periods) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
var dsps []DownsamplingPeriod
|
||||
for _, period := range periods {
|
||||
var dsp DownsamplingPeriod
|
||||
if err := dsp.parse(period); err != nil {
|
||||
return nil, fmt.Errorf("cannot parse downsampling period %q: %w", period, err)
|
||||
}
|
||||
dsps = append(dsps, dsp)
|
||||
}
|
||||
sort.Slice(dsps, func(i, j int) bool {
|
||||
return dsps[i].Offset > dsps[j].Offset
|
||||
})
|
||||
dspPrev := dsps[0]
|
||||
// sanity checks.
|
||||
for _, dsp := range dsps[1:] {
|
||||
if dspPrev.Interval <= dsp.Interval {
|
||||
return nil, fmt.Errorf("prev downsampling interval %d must be bigger than the next interval %d", dspPrev.Interval, dsp.Interval)
|
||||
}
|
||||
if dspPrev.Offset == dsp.Offset {
|
||||
return nil, fmt.Errorf("duplicate downsampling offset: %d", dsp.Offset)
|
||||
}
|
||||
if dspPrev.Interval%dsp.Interval != 0 {
|
||||
return nil, fmt.Errorf("downsamping intervals must be multiples; prev: %d, current: %d", dspPrev.Interval, dsp.Interval)
|
||||
}
|
||||
dspPrev = dsp
|
||||
}
|
||||
return dsps, nil
|
||||
}
|
||||
62
lib/storage/downsampling_test.go
Normal file
62
lib/storage/downsampling_test.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseDownsamplingPeriodsFailure(t *testing.T) {
|
||||
f := func(name string, src []string) {
|
||||
t.Helper()
|
||||
t.Run(name, func(t *testing.T) {
|
||||
if _, err := parseDownsamplingPeriods(src); err == nil {
|
||||
t.Fatalf("want fail for input: %s", strings.Join(src, ","))
|
||||
}
|
||||
})
|
||||
}
|
||||
f("empty duration", []string{"15d"})
|
||||
f("empty interval", []string{":1m"})
|
||||
f("incorrect duration decrease", []string{"30d:15h", "60d:1h"})
|
||||
f("duplicate offset", []string{"30d:15h", "30d:1h"})
|
||||
f("duplicate interval", []string{"60d:1h", "30d:1h"})
|
||||
f("not multiple intervals", []string{"90d:12h", "60:9h", "30d:7h"})
|
||||
}
|
||||
|
||||
func TestParseDownsamplingPeriodsSuccess(t *testing.T) {
|
||||
f := func(name string, src []string, expected []DownsamplingPeriod) {
|
||||
t.Helper()
|
||||
t.Run(name, func(t *testing.T) {
|
||||
dsps, err := parseDownsamplingPeriods(src)
|
||||
if err != nil {
|
||||
t.Fatalf("cannot parse downsampling configuration for: %s, err: %s", strings.Join(src, ","), err)
|
||||
}
|
||||
assertDownsamplingPeriods(t, expected, dsps)
|
||||
})
|
||||
}
|
||||
f("one period", []string{"30d:1m"}, []DownsamplingPeriod{
|
||||
{Offset: 30 * 24 * 3600 * 1000, Interval: 60 * 1000},
|
||||
})
|
||||
f("three periods", []string{"15d:30s", "30d:1m", "60d:15m"}, []DownsamplingPeriod{
|
||||
{Offset: 60 * 24 * 3600 * 1000, Interval: 15 * 60 * 1000},
|
||||
{Offset: 30 * 24 * 3600 * 1000, Interval: 60 * 1000},
|
||||
{Offset: 15 * 24 * 3600 * 1000, Interval: 30 * 1000},
|
||||
})
|
||||
f("with the same divider periods", []string{"15d:1m", "30d:7m", "60d:14m", "90d:28m"}, []DownsamplingPeriod{
|
||||
{Offset: 90 * 24 * 3600 * 1000, Interval: 28 * 60 * 1000},
|
||||
{Offset: 60 * 24 * 3600 * 1000, Interval: 14 * 60 * 1000},
|
||||
{Offset: 30 * 24 * 3600 * 1000, Interval: 7 * 60 * 1000},
|
||||
{Offset: 15 * 24 * 3600 * 1000, Interval: 60 * 1000},
|
||||
})
|
||||
}
|
||||
|
||||
func assertDownsamplingPeriods(t *testing.T, want, got []DownsamplingPeriod) {
|
||||
t.Helper()
|
||||
if len(want) != len(got) {
|
||||
t.Fatalf("len mismatch, want: %d, got: %d", len(want), len(got))
|
||||
}
|
||||
for i := 0; i < len(want); i++ {
|
||||
if want[i] != got[i] {
|
||||
t.Fatalf("want period: %s, got period: %s, idx: %d", want[i], got[i], i)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -408,6 +408,12 @@ func (mn *MetricName) String() string {
|
||||
return fmt.Sprintf("%s{%s}", mnCopy.MetricGroup, tagsStr)
|
||||
}
|
||||
|
||||
// SortAndMarshal sorts mn tags and then marshals them to dst.
|
||||
func (mn *MetricName) SortAndMarshal(dst []byte) []byte {
|
||||
mn.sortTags()
|
||||
return mn.Marshal(dst)
|
||||
}
|
||||
|
||||
// Marshal appends marshaled mn to dst and returns the result.
|
||||
//
|
||||
// mn.sortTags must be called before calling this function
|
||||
|
||||
@@ -1344,7 +1344,7 @@ func (pt *partition) runFinalDedup(stopCh <-chan struct{}) error {
|
||||
}
|
||||
|
||||
func (pt *partition) isFinalDedupNeeded() bool {
|
||||
dedupInterval := GetDedupInterval()
|
||||
dedupInterval := GetDedupInterval(pt.tr.MaxTimestamp)
|
||||
|
||||
pws := pt.GetParts(nil, false)
|
||||
minDedupInterval := getMinDedupInterval(pws)
|
||||
@@ -1577,7 +1577,7 @@ func (pt *partition) mergePartsInternal(dstPartPath string, bsw *blockStreamWrit
|
||||
return nil, fmt.Errorf("cannot merge %d parts to %s: %w", len(bsrs), dstPartPath, err)
|
||||
}
|
||||
if dstPartPath != "" {
|
||||
ph.MinDedupInterval = GetDedupInterval()
|
||||
ph.MinDedupInterval = GetDedupInterval(ph.MaxTimestamp)
|
||||
ph.MustWriteMetadata(dstPartPath)
|
||||
}
|
||||
return &ph, nil
|
||||
|
||||
22
vendor/github.com/go-kit/kit/LICENSE
generated
vendored
Normal file
22
vendor/github.com/go-kit/kit/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Peter Bourgon
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
151
vendor/github.com/go-kit/kit/log/README.md
generated
vendored
Normal file
151
vendor/github.com/go-kit/kit/log/README.md
generated
vendored
Normal file
@@ -0,0 +1,151 @@
|
||||
# package log
|
||||
|
||||
`package log` provides a minimal interface for structured logging in services.
|
||||
It may be wrapped to encode conventions, enforce type-safety, provide leveled
|
||||
logging, and so on. It can be used for both typical application log events,
|
||||
and log-structured data streams.
|
||||
|
||||
## Structured logging
|
||||
|
||||
Structured logging is, basically, conceding to the reality that logs are
|
||||
_data_, and warrant some level of schematic rigor. Using a stricter,
|
||||
key/value-oriented message format for our logs, containing contextual and
|
||||
semantic information, makes it much easier to get insight into the
|
||||
operational activity of the systems we build. Consequently, `package log` is
|
||||
of the strong belief that "[the benefits of structured logging outweigh the
|
||||
minimal effort involved](https://www.thoughtworks.com/radar/techniques/structured-logging)".
|
||||
|
||||
Migrating from unstructured to structured logging is probably a lot easier
|
||||
than you'd expect.
|
||||
|
||||
```go
|
||||
// Unstructured
|
||||
log.Printf("HTTP server listening on %s", addr)
|
||||
|
||||
// Structured
|
||||
logger.Log("transport", "HTTP", "addr", addr, "msg", "listening")
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Typical application logging
|
||||
|
||||
```go
|
||||
w := log.NewSyncWriter(os.Stderr)
|
||||
logger := log.NewLogfmtLogger(w)
|
||||
logger.Log("question", "what is the meaning of life?", "answer", 42)
|
||||
|
||||
// Output:
|
||||
// question="what is the meaning of life?" answer=42
|
||||
```
|
||||
|
||||
### Contextual Loggers
|
||||
|
||||
```go
|
||||
func main() {
|
||||
var logger log.Logger
|
||||
logger = log.NewLogfmtLogger(log.NewSyncWriter(os.Stderr))
|
||||
logger = log.With(logger, "instance_id", 123)
|
||||
|
||||
logger.Log("msg", "starting")
|
||||
NewWorker(log.With(logger, "component", "worker")).Run()
|
||||
NewSlacker(log.With(logger, "component", "slacker")).Run()
|
||||
}
|
||||
|
||||
// Output:
|
||||
// instance_id=123 msg=starting
|
||||
// instance_id=123 component=worker msg=running
|
||||
// instance_id=123 component=slacker msg=running
|
||||
```
|
||||
|
||||
### Interact with stdlib logger
|
||||
|
||||
Redirect stdlib logger to Go kit logger.
|
||||
|
||||
```go
|
||||
import (
|
||||
"os"
|
||||
stdlog "log"
|
||||
kitlog "github.com/go-kit/kit/log"
|
||||
)
|
||||
|
||||
func main() {
|
||||
logger := kitlog.NewJSONLogger(kitlog.NewSyncWriter(os.Stdout))
|
||||
stdlog.SetOutput(kitlog.NewStdlibAdapter(logger))
|
||||
stdlog.Print("I sure like pie")
|
||||
}
|
||||
|
||||
// Output:
|
||||
// {"msg":"I sure like pie","ts":"2016/01/01 12:34:56"}
|
||||
```
|
||||
|
||||
Or, if, for legacy reasons, you need to pipe all of your logging through the
|
||||
stdlib log package, you can redirect Go kit logger to the stdlib logger.
|
||||
|
||||
```go
|
||||
logger := kitlog.NewLogfmtLogger(kitlog.StdlibWriter{})
|
||||
logger.Log("legacy", true, "msg", "at least it's something")
|
||||
|
||||
// Output:
|
||||
// 2016/01/01 12:34:56 legacy=true msg="at least it's something"
|
||||
```
|
||||
|
||||
### Timestamps and callers
|
||||
|
||||
```go
|
||||
var logger log.Logger
|
||||
logger = log.NewLogfmtLogger(log.NewSyncWriter(os.Stderr))
|
||||
logger = log.With(logger, "ts", log.DefaultTimestampUTC, "caller", log.DefaultCaller)
|
||||
|
||||
logger.Log("msg", "hello")
|
||||
|
||||
// Output:
|
||||
// ts=2016-01-01T12:34:56Z caller=main.go:15 msg=hello
|
||||
```
|
||||
|
||||
## Levels
|
||||
|
||||
Log levels are supported via the [level package](https://godoc.org/github.com/go-kit/kit/log/level).
|
||||
|
||||
## Supported output formats
|
||||
|
||||
- [Logfmt](https://brandur.org/logfmt) ([see also](https://blog.codeship.com/logfmt-a-log-format-thats-easy-to-read-and-write))
|
||||
- JSON
|
||||
|
||||
## Enhancements
|
||||
|
||||
`package log` is centered on the one-method Logger interface.
|
||||
|
||||
```go
|
||||
type Logger interface {
|
||||
Log(keyvals ...interface{}) error
|
||||
}
|
||||
```
|
||||
|
||||
This interface, and its supporting code like is the product of much iteration
|
||||
and evaluation. For more details on the evolution of the Logger interface,
|
||||
see [The Hunt for a Logger Interface](http://go-talks.appspot.com/github.com/ChrisHines/talks/structured-logging/structured-logging.slide#1),
|
||||
a talk by [Chris Hines](https://github.com/ChrisHines).
|
||||
Also, please see
|
||||
[#63](https://github.com/go-kit/kit/issues/63),
|
||||
[#76](https://github.com/go-kit/kit/pull/76),
|
||||
[#131](https://github.com/go-kit/kit/issues/131),
|
||||
[#157](https://github.com/go-kit/kit/pull/157),
|
||||
[#164](https://github.com/go-kit/kit/issues/164), and
|
||||
[#252](https://github.com/go-kit/kit/pull/252)
|
||||
to review historical conversations about package log and the Logger interface.
|
||||
|
||||
Value-add packages and suggestions,
|
||||
like improvements to [the leveled logger](https://godoc.org/github.com/go-kit/kit/log/level),
|
||||
are of course welcome. Good proposals should
|
||||
|
||||
- Be composable with [contextual loggers](https://godoc.org/github.com/go-kit/kit/log#With),
|
||||
- Not break the behavior of [log.Caller](https://godoc.org/github.com/go-kit/kit/log#Caller) in any wrapped contextual loggers, and
|
||||
- Be friendly to packages that accept only an unadorned log.Logger.
|
||||
|
||||
## Benchmarks & comparisons
|
||||
|
||||
There are a few Go logging benchmarks and comparisons that include Go kit's package log.
|
||||
|
||||
- [imkira/go-loggers-bench](https://github.com/imkira/go-loggers-bench) includes kit/log
|
||||
- [uber-common/zap](https://github.com/uber-common/zap), a zero-alloc logging library, includes a comparison with kit/log
|
||||
116
vendor/github.com/go-kit/kit/log/doc.go
generated
vendored
Normal file
116
vendor/github.com/go-kit/kit/log/doc.go
generated
vendored
Normal file
@@ -0,0 +1,116 @@
|
||||
// Package log provides a structured logger.
|
||||
//
|
||||
// Structured logging produces logs easily consumed later by humans or
|
||||
// machines. Humans might be interested in debugging errors, or tracing
|
||||
// specific requests. Machines might be interested in counting interesting
|
||||
// events, or aggregating information for off-line processing. In both cases,
|
||||
// it is important that the log messages are structured and actionable.
|
||||
// Package log is designed to encourage both of these best practices.
|
||||
//
|
||||
// Basic Usage
|
||||
//
|
||||
// The fundamental interface is Logger. Loggers create log events from
|
||||
// key/value data. The Logger interface has a single method, Log, which
|
||||
// accepts a sequence of alternating key/value pairs, which this package names
|
||||
// keyvals.
|
||||
//
|
||||
// type Logger interface {
|
||||
// Log(keyvals ...interface{}) error
|
||||
// }
|
||||
//
|
||||
// Here is an example of a function using a Logger to create log events.
|
||||
//
|
||||
// func RunTask(task Task, logger log.Logger) string {
|
||||
// logger.Log("taskID", task.ID, "event", "starting task")
|
||||
// ...
|
||||
// logger.Log("taskID", task.ID, "event", "task complete")
|
||||
// }
|
||||
//
|
||||
// The keys in the above example are "taskID" and "event". The values are
|
||||
// task.ID, "starting task", and "task complete". Every key is followed
|
||||
// immediately by its value.
|
||||
//
|
||||
// Keys are usually plain strings. Values may be any type that has a sensible
|
||||
// encoding in the chosen log format. With structured logging it is a good
|
||||
// idea to log simple values without formatting them. This practice allows
|
||||
// the chosen logger to encode values in the most appropriate way.
|
||||
//
|
||||
// Contextual Loggers
|
||||
//
|
||||
// A contextual logger stores keyvals that it includes in all log events.
|
||||
// Building appropriate contextual loggers reduces repetition and aids
|
||||
// consistency in the resulting log output. With and WithPrefix add context to
|
||||
// a logger. We can use With to improve the RunTask example.
|
||||
//
|
||||
// func RunTask(task Task, logger log.Logger) string {
|
||||
// logger = log.With(logger, "taskID", task.ID)
|
||||
// logger.Log("event", "starting task")
|
||||
// ...
|
||||
// taskHelper(task.Cmd, logger)
|
||||
// ...
|
||||
// logger.Log("event", "task complete")
|
||||
// }
|
||||
//
|
||||
// The improved version emits the same log events as the original for the
|
||||
// first and last calls to Log. Passing the contextual logger to taskHelper
|
||||
// enables each log event created by taskHelper to include the task.ID even
|
||||
// though taskHelper does not have access to that value. Using contextual
|
||||
// loggers this way simplifies producing log output that enables tracing the
|
||||
// life cycle of individual tasks. (See the Contextual example for the full
|
||||
// code of the above snippet.)
|
||||
//
|
||||
// Dynamic Contextual Values
|
||||
//
|
||||
// A Valuer function stored in a contextual logger generates a new value each
|
||||
// time an event is logged. The Valuer example demonstrates how this feature
|
||||
// works.
|
||||
//
|
||||
// Valuers provide the basis for consistently logging timestamps and source
|
||||
// code location. The log package defines several valuers for that purpose.
|
||||
// See Timestamp, DefaultTimestamp, DefaultTimestampUTC, Caller, and
|
||||
// DefaultCaller. A common logger initialization sequence that ensures all log
|
||||
// entries contain a timestamp and source location looks like this:
|
||||
//
|
||||
// logger := log.NewLogfmtLogger(log.NewSyncWriter(os.Stdout))
|
||||
// logger = log.With(logger, "ts", log.DefaultTimestampUTC, "caller", log.DefaultCaller)
|
||||
//
|
||||
// Concurrent Safety
|
||||
//
|
||||
// Applications with multiple goroutines want each log event written to the
|
||||
// same logger to remain separate from other log events. Package log provides
|
||||
// two simple solutions for concurrent safe logging.
|
||||
//
|
||||
// NewSyncWriter wraps an io.Writer and serializes each call to its Write
|
||||
// method. Using a SyncWriter has the benefit that the smallest practical
|
||||
// portion of the logging logic is performed within a mutex, but it requires
|
||||
// the formatting Logger to make only one call to Write per log event.
|
||||
//
|
||||
// NewSyncLogger wraps any Logger and serializes each call to its Log method.
|
||||
// Using a SyncLogger has the benefit that it guarantees each log event is
|
||||
// handled atomically within the wrapped logger, but it typically serializes
|
||||
// both the formatting and output logic. Use a SyncLogger if the formatting
|
||||
// logger may perform multiple writes per log event.
|
||||
//
|
||||
// Error Handling
|
||||
//
|
||||
// This package relies on the practice of wrapping or decorating loggers with
|
||||
// other loggers to provide composable pieces of functionality. It also means
|
||||
// that Logger.Log must return an error because some
|
||||
// implementations—especially those that output log data to an io.Writer—may
|
||||
// encounter errors that cannot be handled locally. This in turn means that
|
||||
// Loggers that wrap other loggers should return errors from the wrapped
|
||||
// logger up the stack.
|
||||
//
|
||||
// Fortunately, the decorator pattern also provides a way to avoid the
|
||||
// necessity to check for errors every time an application calls Logger.Log.
|
||||
// An application required to panic whenever its Logger encounters
|
||||
// an error could initialize its logger as follows.
|
||||
//
|
||||
// fmtlogger := log.NewLogfmtLogger(log.NewSyncWriter(os.Stdout))
|
||||
// logger := log.LoggerFunc(func(keyvals ...interface{}) error {
|
||||
// if err := fmtlogger.Log(keyvals...); err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
// return nil
|
||||
// })
|
||||
package log
|
||||
89
vendor/github.com/go-kit/kit/log/json_logger.go
generated
vendored
Normal file
89
vendor/github.com/go-kit/kit/log/json_logger.go
generated
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
type jsonLogger struct {
|
||||
io.Writer
|
||||
}
|
||||
|
||||
// NewJSONLogger returns a Logger that encodes keyvals to the Writer as a
|
||||
// single JSON object. Each log event produces no more than one call to
|
||||
// w.Write. The passed Writer must be safe for concurrent use by multiple
|
||||
// goroutines if the returned Logger will be used concurrently.
|
||||
func NewJSONLogger(w io.Writer) Logger {
|
||||
return &jsonLogger{w}
|
||||
}
|
||||
|
||||
func (l *jsonLogger) Log(keyvals ...interface{}) error {
|
||||
n := (len(keyvals) + 1) / 2 // +1 to handle case when len is odd
|
||||
m := make(map[string]interface{}, n)
|
||||
for i := 0; i < len(keyvals); i += 2 {
|
||||
k := keyvals[i]
|
||||
var v interface{} = ErrMissingValue
|
||||
if i+1 < len(keyvals) {
|
||||
v = keyvals[i+1]
|
||||
}
|
||||
merge(m, k, v)
|
||||
}
|
||||
return json.NewEncoder(l.Writer).Encode(m)
|
||||
}
|
||||
|
||||
func merge(dst map[string]interface{}, k, v interface{}) {
|
||||
var key string
|
||||
switch x := k.(type) {
|
||||
case string:
|
||||
key = x
|
||||
case fmt.Stringer:
|
||||
key = safeString(x)
|
||||
default:
|
||||
key = fmt.Sprint(x)
|
||||
}
|
||||
|
||||
// We want json.Marshaler and encoding.TextMarshaller to take priority over
|
||||
// err.Error() and v.String(). But json.Marshall (called later) does that by
|
||||
// default so we force a no-op if it's one of those 2 case.
|
||||
switch x := v.(type) {
|
||||
case json.Marshaler:
|
||||
case encoding.TextMarshaler:
|
||||
case error:
|
||||
v = safeError(x)
|
||||
case fmt.Stringer:
|
||||
v = safeString(x)
|
||||
}
|
||||
|
||||
dst[key] = v
|
||||
}
|
||||
|
||||
func safeString(str fmt.Stringer) (s string) {
|
||||
defer func() {
|
||||
if panicVal := recover(); panicVal != nil {
|
||||
if v := reflect.ValueOf(str); v.Kind() == reflect.Ptr && v.IsNil() {
|
||||
s = "NULL"
|
||||
} else {
|
||||
panic(panicVal)
|
||||
}
|
||||
}
|
||||
}()
|
||||
s = str.String()
|
||||
return
|
||||
}
|
||||
|
||||
func safeError(err error) (s interface{}) {
|
||||
defer func() {
|
||||
if panicVal := recover(); panicVal != nil {
|
||||
if v := reflect.ValueOf(err); v.Kind() == reflect.Ptr && v.IsNil() {
|
||||
s = nil
|
||||
} else {
|
||||
panic(panicVal)
|
||||
}
|
||||
}
|
||||
}()
|
||||
s = err.Error()
|
||||
return
|
||||
}
|
||||
135
vendor/github.com/go-kit/kit/log/log.go
generated
vendored
Normal file
135
vendor/github.com/go-kit/kit/log/log.go
generated
vendored
Normal file
@@ -0,0 +1,135 @@
|
||||
package log
|
||||
|
||||
import "errors"
|
||||
|
||||
// Logger is the fundamental interface for all log operations. Log creates a
|
||||
// log event from keyvals, a variadic sequence of alternating keys and values.
|
||||
// Implementations must be safe for concurrent use by multiple goroutines. In
|
||||
// particular, any implementation of Logger that appends to keyvals or
|
||||
// modifies or retains any of its elements must make a copy first.
|
||||
type Logger interface {
|
||||
Log(keyvals ...interface{}) error
|
||||
}
|
||||
|
||||
// ErrMissingValue is appended to keyvals slices with odd length to substitute
|
||||
// the missing value.
|
||||
var ErrMissingValue = errors.New("(MISSING)")
|
||||
|
||||
// With returns a new contextual logger with keyvals prepended to those passed
|
||||
// to calls to Log. If logger is also a contextual logger created by With or
|
||||
// WithPrefix, keyvals is appended to the existing context.
|
||||
//
|
||||
// The returned Logger replaces all value elements (odd indexes) containing a
|
||||
// Valuer with their generated value for each call to its Log method.
|
||||
func With(logger Logger, keyvals ...interface{}) Logger {
|
||||
if len(keyvals) == 0 {
|
||||
return logger
|
||||
}
|
||||
l := newContext(logger)
|
||||
kvs := append(l.keyvals, keyvals...)
|
||||
if len(kvs)%2 != 0 {
|
||||
kvs = append(kvs, ErrMissingValue)
|
||||
}
|
||||
return &context{
|
||||
logger: l.logger,
|
||||
// Limiting the capacity of the stored keyvals ensures that a new
|
||||
// backing array is created if the slice must grow in Log or With.
|
||||
// Using the extra capacity without copying risks a data race that
|
||||
// would violate the Logger interface contract.
|
||||
keyvals: kvs[:len(kvs):len(kvs)],
|
||||
hasValuer: l.hasValuer || containsValuer(keyvals),
|
||||
}
|
||||
}
|
||||
|
||||
// WithPrefix returns a new contextual logger with keyvals prepended to those
|
||||
// passed to calls to Log. If logger is also a contextual logger created by
|
||||
// With or WithPrefix, keyvals is prepended to the existing context.
|
||||
//
|
||||
// The returned Logger replaces all value elements (odd indexes) containing a
|
||||
// Valuer with their generated value for each call to its Log method.
|
||||
func WithPrefix(logger Logger, keyvals ...interface{}) Logger {
|
||||
if len(keyvals) == 0 {
|
||||
return logger
|
||||
}
|
||||
l := newContext(logger)
|
||||
// Limiting the capacity of the stored keyvals ensures that a new
|
||||
// backing array is created if the slice must grow in Log or With.
|
||||
// Using the extra capacity without copying risks a data race that
|
||||
// would violate the Logger interface contract.
|
||||
n := len(l.keyvals) + len(keyvals)
|
||||
if len(keyvals)%2 != 0 {
|
||||
n++
|
||||
}
|
||||
kvs := make([]interface{}, 0, n)
|
||||
kvs = append(kvs, keyvals...)
|
||||
if len(kvs)%2 != 0 {
|
||||
kvs = append(kvs, ErrMissingValue)
|
||||
}
|
||||
kvs = append(kvs, l.keyvals...)
|
||||
return &context{
|
||||
logger: l.logger,
|
||||
keyvals: kvs,
|
||||
hasValuer: l.hasValuer || containsValuer(keyvals),
|
||||
}
|
||||
}
|
||||
|
||||
// context is the Logger implementation returned by With and WithPrefix. It
|
||||
// wraps a Logger and holds keyvals that it includes in all log events. Its
|
||||
// Log method calls bindValues to generate values for each Valuer in the
|
||||
// context keyvals.
|
||||
//
|
||||
// A context must always have the same number of stack frames between calls to
|
||||
// its Log method and the eventual binding of Valuers to their value. This
|
||||
// requirement comes from the functional requirement to allow a context to
|
||||
// resolve application call site information for a Caller stored in the
|
||||
// context. To do this we must be able to predict the number of logging
|
||||
// functions on the stack when bindValues is called.
|
||||
//
|
||||
// Two implementation details provide the needed stack depth consistency.
|
||||
//
|
||||
// 1. newContext avoids introducing an additional layer when asked to
|
||||
// wrap another context.
|
||||
// 2. With and WithPrefix avoid introducing an additional layer by
|
||||
// returning a newly constructed context with a merged keyvals rather
|
||||
// than simply wrapping the existing context.
|
||||
type context struct {
|
||||
logger Logger
|
||||
keyvals []interface{}
|
||||
hasValuer bool
|
||||
}
|
||||
|
||||
func newContext(logger Logger) *context {
|
||||
if c, ok := logger.(*context); ok {
|
||||
return c
|
||||
}
|
||||
return &context{logger: logger}
|
||||
}
|
||||
|
||||
// Log replaces all value elements (odd indexes) containing a Valuer in the
|
||||
// stored context with their generated value, appends keyvals, and passes the
|
||||
// result to the wrapped Logger.
|
||||
func (l *context) Log(keyvals ...interface{}) error {
|
||||
kvs := append(l.keyvals, keyvals...)
|
||||
if len(kvs)%2 != 0 {
|
||||
kvs = append(kvs, ErrMissingValue)
|
||||
}
|
||||
if l.hasValuer {
|
||||
// If no keyvals were appended above then we must copy l.keyvals so
|
||||
// that future log events will reevaluate the stored Valuers.
|
||||
if len(keyvals) == 0 {
|
||||
kvs = append([]interface{}{}, l.keyvals...)
|
||||
}
|
||||
bindValues(kvs[:len(l.keyvals)])
|
||||
}
|
||||
return l.logger.Log(kvs...)
|
||||
}
|
||||
|
||||
// LoggerFunc is an adapter to allow use of ordinary functions as Loggers. If
|
||||
// f is a function with the appropriate signature, LoggerFunc(f) is a Logger
|
||||
// object that calls f.
|
||||
type LoggerFunc func(...interface{}) error
|
||||
|
||||
// Log implements Logger by calling f(keyvals...).
|
||||
func (f LoggerFunc) Log(keyvals ...interface{}) error {
|
||||
return f(keyvals...)
|
||||
}
|
||||
62
vendor/github.com/go-kit/kit/log/logfmt_logger.go
generated
vendored
Normal file
62
vendor/github.com/go-kit/kit/log/logfmt_logger.go
generated
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"github.com/go-logfmt/logfmt"
|
||||
)
|
||||
|
||||
type logfmtEncoder struct {
|
||||
*logfmt.Encoder
|
||||
buf bytes.Buffer
|
||||
}
|
||||
|
||||
func (l *logfmtEncoder) Reset() {
|
||||
l.Encoder.Reset()
|
||||
l.buf.Reset()
|
||||
}
|
||||
|
||||
var logfmtEncoderPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
var enc logfmtEncoder
|
||||
enc.Encoder = logfmt.NewEncoder(&enc.buf)
|
||||
return &enc
|
||||
},
|
||||
}
|
||||
|
||||
type logfmtLogger struct {
|
||||
w io.Writer
|
||||
}
|
||||
|
||||
// NewLogfmtLogger returns a logger that encodes keyvals to the Writer in
|
||||
// logfmt format. Each log event produces no more than one call to w.Write.
|
||||
// The passed Writer must be safe for concurrent use by multiple goroutines if
|
||||
// the returned Logger will be used concurrently.
|
||||
func NewLogfmtLogger(w io.Writer) Logger {
|
||||
return &logfmtLogger{w}
|
||||
}
|
||||
|
||||
func (l logfmtLogger) Log(keyvals ...interface{}) error {
|
||||
enc := logfmtEncoderPool.Get().(*logfmtEncoder)
|
||||
enc.Reset()
|
||||
defer logfmtEncoderPool.Put(enc)
|
||||
|
||||
if err := enc.EncodeKeyvals(keyvals...); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Add newline to the end of the buffer
|
||||
if err := enc.EndRecord(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// The Logger interface requires implementations to be safe for concurrent
|
||||
// use by multiple goroutines. For this implementation that means making
|
||||
// only one call to l.w.Write() for each call to Log.
|
||||
if _, err := l.w.Write(enc.buf.Bytes()); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
8
vendor/github.com/go-kit/kit/log/nop_logger.go
generated
vendored
Normal file
8
vendor/github.com/go-kit/kit/log/nop_logger.go
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
package log
|
||||
|
||||
type nopLogger struct{}
|
||||
|
||||
// NewNopLogger returns a logger that doesn't do anything.
|
||||
func NewNopLogger() Logger { return nopLogger{} }
|
||||
|
||||
func (nopLogger) Log(...interface{}) error { return nil }
|
||||
116
vendor/github.com/go-kit/kit/log/stdlib.go
generated
vendored
Normal file
116
vendor/github.com/go-kit/kit/log/stdlib.go
generated
vendored
Normal file
@@ -0,0 +1,116 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// StdlibWriter implements io.Writer by invoking the stdlib log.Print. It's
|
||||
// designed to be passed to a Go kit logger as the writer, for cases where
|
||||
// it's necessary to redirect all Go kit log output to the stdlib logger.
|
||||
//
|
||||
// If you have any choice in the matter, you shouldn't use this. Prefer to
|
||||
// redirect the stdlib log to the Go kit logger via NewStdlibAdapter.
|
||||
type StdlibWriter struct{}
|
||||
|
||||
// Write implements io.Writer.
|
||||
func (w StdlibWriter) Write(p []byte) (int, error) {
|
||||
log.Print(strings.TrimSpace(string(p)))
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
// StdlibAdapter wraps a Logger and allows it to be passed to the stdlib
|
||||
// logger's SetOutput. It will extract date/timestamps, filenames, and
|
||||
// messages, and place them under relevant keys.
|
||||
type StdlibAdapter struct {
|
||||
Logger
|
||||
timestampKey string
|
||||
fileKey string
|
||||
messageKey string
|
||||
}
|
||||
|
||||
// StdlibAdapterOption sets a parameter for the StdlibAdapter.
|
||||
type StdlibAdapterOption func(*StdlibAdapter)
|
||||
|
||||
// TimestampKey sets the key for the timestamp field. By default, it's "ts".
|
||||
func TimestampKey(key string) StdlibAdapterOption {
|
||||
return func(a *StdlibAdapter) { a.timestampKey = key }
|
||||
}
|
||||
|
||||
// FileKey sets the key for the file and line field. By default, it's "caller".
|
||||
func FileKey(key string) StdlibAdapterOption {
|
||||
return func(a *StdlibAdapter) { a.fileKey = key }
|
||||
}
|
||||
|
||||
// MessageKey sets the key for the actual log message. By default, it's "msg".
|
||||
func MessageKey(key string) StdlibAdapterOption {
|
||||
return func(a *StdlibAdapter) { a.messageKey = key }
|
||||
}
|
||||
|
||||
// NewStdlibAdapter returns a new StdlibAdapter wrapper around the passed
|
||||
// logger. It's designed to be passed to log.SetOutput.
|
||||
func NewStdlibAdapter(logger Logger, options ...StdlibAdapterOption) io.Writer {
|
||||
a := StdlibAdapter{
|
||||
Logger: logger,
|
||||
timestampKey: "ts",
|
||||
fileKey: "caller",
|
||||
messageKey: "msg",
|
||||
}
|
||||
for _, option := range options {
|
||||
option(&a)
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func (a StdlibAdapter) Write(p []byte) (int, error) {
|
||||
result := subexps(p)
|
||||
keyvals := []interface{}{}
|
||||
var timestamp string
|
||||
if date, ok := result["date"]; ok && date != "" {
|
||||
timestamp = date
|
||||
}
|
||||
if time, ok := result["time"]; ok && time != "" {
|
||||
if timestamp != "" {
|
||||
timestamp += " "
|
||||
}
|
||||
timestamp += time
|
||||
}
|
||||
if timestamp != "" {
|
||||
keyvals = append(keyvals, a.timestampKey, timestamp)
|
||||
}
|
||||
if file, ok := result["file"]; ok && file != "" {
|
||||
keyvals = append(keyvals, a.fileKey, file)
|
||||
}
|
||||
if msg, ok := result["msg"]; ok {
|
||||
keyvals = append(keyvals, a.messageKey, msg)
|
||||
}
|
||||
if err := a.Logger.Log(keyvals...); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
const (
|
||||
logRegexpDate = `(?P<date>[0-9]{4}/[0-9]{2}/[0-9]{2})?[ ]?`
|
||||
logRegexpTime = `(?P<time>[0-9]{2}:[0-9]{2}:[0-9]{2}(\.[0-9]+)?)?[ ]?`
|
||||
logRegexpFile = `(?P<file>.+?:[0-9]+)?`
|
||||
logRegexpMsg = `(: )?(?P<msg>.*)`
|
||||
)
|
||||
|
||||
var (
|
||||
logRegexp = regexp.MustCompile(logRegexpDate + logRegexpTime + logRegexpFile + logRegexpMsg)
|
||||
)
|
||||
|
||||
func subexps(line []byte) map[string]string {
|
||||
m := logRegexp.FindSubmatch(line)
|
||||
if len(m) < len(logRegexp.SubexpNames()) {
|
||||
return map[string]string{}
|
||||
}
|
||||
result := map[string]string{}
|
||||
for i, name := range logRegexp.SubexpNames() {
|
||||
result[name] = string(m[i])
|
||||
}
|
||||
return result
|
||||
}
|
||||
116
vendor/github.com/go-kit/kit/log/sync.go
generated
vendored
Normal file
116
vendor/github.com/go-kit/kit/log/sync.go
generated
vendored
Normal file
@@ -0,0 +1,116 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// SwapLogger wraps another logger that may be safely replaced while other
|
||||
// goroutines use the SwapLogger concurrently. The zero value for a SwapLogger
|
||||
// will discard all log events without error.
|
||||
//
|
||||
// SwapLogger serves well as a package global logger that can be changed by
|
||||
// importers.
|
||||
type SwapLogger struct {
|
||||
logger atomic.Value
|
||||
}
|
||||
|
||||
type loggerStruct struct {
|
||||
Logger
|
||||
}
|
||||
|
||||
// Log implements the Logger interface by forwarding keyvals to the currently
|
||||
// wrapped logger. It does not log anything if the wrapped logger is nil.
|
||||
func (l *SwapLogger) Log(keyvals ...interface{}) error {
|
||||
s, ok := l.logger.Load().(loggerStruct)
|
||||
if !ok || s.Logger == nil {
|
||||
return nil
|
||||
}
|
||||
return s.Log(keyvals...)
|
||||
}
|
||||
|
||||
// Swap replaces the currently wrapped logger with logger. Swap may be called
|
||||
// concurrently with calls to Log from other goroutines.
|
||||
func (l *SwapLogger) Swap(logger Logger) {
|
||||
l.logger.Store(loggerStruct{logger})
|
||||
}
|
||||
|
||||
// NewSyncWriter returns a new writer that is safe for concurrent use by
|
||||
// multiple goroutines. Writes to the returned writer are passed on to w. If
|
||||
// another write is already in progress, the calling goroutine blocks until
|
||||
// the writer is available.
|
||||
//
|
||||
// If w implements the following interface, so does the returned writer.
|
||||
//
|
||||
// interface {
|
||||
// Fd() uintptr
|
||||
// }
|
||||
func NewSyncWriter(w io.Writer) io.Writer {
|
||||
switch w := w.(type) {
|
||||
case fdWriter:
|
||||
return &fdSyncWriter{fdWriter: w}
|
||||
default:
|
||||
return &syncWriter{Writer: w}
|
||||
}
|
||||
}
|
||||
|
||||
// syncWriter synchronizes concurrent writes to an io.Writer.
|
||||
type syncWriter struct {
|
||||
sync.Mutex
|
||||
io.Writer
|
||||
}
|
||||
|
||||
// Write writes p to the underlying io.Writer. If another write is already in
|
||||
// progress, the calling goroutine blocks until the syncWriter is available.
|
||||
func (w *syncWriter) Write(p []byte) (n int, err error) {
|
||||
w.Lock()
|
||||
n, err = w.Writer.Write(p)
|
||||
w.Unlock()
|
||||
return n, err
|
||||
}
|
||||
|
||||
// fdWriter is an io.Writer that also has an Fd method. The most common
|
||||
// example of an fdWriter is an *os.File.
|
||||
type fdWriter interface {
|
||||
io.Writer
|
||||
Fd() uintptr
|
||||
}
|
||||
|
||||
// fdSyncWriter synchronizes concurrent writes to an fdWriter.
|
||||
type fdSyncWriter struct {
|
||||
sync.Mutex
|
||||
fdWriter
|
||||
}
|
||||
|
||||
// Write writes p to the underlying io.Writer. If another write is already in
|
||||
// progress, the calling goroutine blocks until the fdSyncWriter is available.
|
||||
func (w *fdSyncWriter) Write(p []byte) (n int, err error) {
|
||||
w.Lock()
|
||||
n, err = w.fdWriter.Write(p)
|
||||
w.Unlock()
|
||||
return n, err
|
||||
}
|
||||
|
||||
// syncLogger provides concurrent safe logging for another Logger.
|
||||
type syncLogger struct {
|
||||
mu sync.Mutex
|
||||
logger Logger
|
||||
}
|
||||
|
||||
// NewSyncLogger returns a logger that synchronizes concurrent use of the
|
||||
// wrapped logger. When multiple goroutines use the SyncLogger concurrently
|
||||
// only one goroutine will be allowed to log to the wrapped logger at a time.
|
||||
// The other goroutines will block until the logger is available.
|
||||
func NewSyncLogger(logger Logger) Logger {
|
||||
return &syncLogger{logger: logger}
|
||||
}
|
||||
|
||||
// Log logs keyvals to the underlying Logger. If another log is already in
|
||||
// progress, the calling goroutine blocks until the syncLogger is available.
|
||||
func (l *syncLogger) Log(keyvals ...interface{}) error {
|
||||
l.mu.Lock()
|
||||
err := l.logger.Log(keyvals...)
|
||||
l.mu.Unlock()
|
||||
return err
|
||||
}
|
||||
110
vendor/github.com/go-kit/kit/log/value.go
generated
vendored
Normal file
110
vendor/github.com/go-kit/kit/log/value.go
generated
vendored
Normal file
@@ -0,0 +1,110 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// A Valuer generates a log value. When passed to With or WithPrefix in a
|
||||
// value element (odd indexes), it represents a dynamic value which is re-
|
||||
// evaluated with each log event.
|
||||
type Valuer func() interface{}
|
||||
|
||||
// bindValues replaces all value elements (odd indexes) containing a Valuer
|
||||
// with their generated value.
|
||||
func bindValues(keyvals []interface{}) {
|
||||
for i := 1; i < len(keyvals); i += 2 {
|
||||
if v, ok := keyvals[i].(Valuer); ok {
|
||||
keyvals[i] = v()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// containsValuer returns true if any of the value elements (odd indexes)
|
||||
// contain a Valuer.
|
||||
func containsValuer(keyvals []interface{}) bool {
|
||||
for i := 1; i < len(keyvals); i += 2 {
|
||||
if _, ok := keyvals[i].(Valuer); ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Timestamp returns a timestamp Valuer. It invokes the t function to get the
|
||||
// time; unless you are doing something tricky, pass time.Now.
|
||||
//
|
||||
// Most users will want to use DefaultTimestamp or DefaultTimestampUTC, which
|
||||
// are TimestampFormats that use the RFC3339Nano format.
|
||||
func Timestamp(t func() time.Time) Valuer {
|
||||
return func() interface{} { return t() }
|
||||
}
|
||||
|
||||
// TimestampFormat returns a timestamp Valuer with a custom time format. It
|
||||
// invokes the t function to get the time to format; unless you are doing
|
||||
// something tricky, pass time.Now. The layout string is passed to
|
||||
// Time.Format.
|
||||
//
|
||||
// Most users will want to use DefaultTimestamp or DefaultTimestampUTC, which
|
||||
// are TimestampFormats that use the RFC3339Nano format.
|
||||
func TimestampFormat(t func() time.Time, layout string) Valuer {
|
||||
return func() interface{} {
|
||||
return timeFormat{
|
||||
time: t(),
|
||||
layout: layout,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A timeFormat represents an instant in time and a layout used when
|
||||
// marshaling to a text format.
|
||||
type timeFormat struct {
|
||||
time time.Time
|
||||
layout string
|
||||
}
|
||||
|
||||
func (tf timeFormat) String() string {
|
||||
return tf.time.Format(tf.layout)
|
||||
}
|
||||
|
||||
// MarshalText implements encoding.TextMarshaller.
|
||||
func (tf timeFormat) MarshalText() (text []byte, err error) {
|
||||
// The following code adapted from the standard library time.Time.Format
|
||||
// method. Using the same undocumented magic constant to extend the size
|
||||
// of the buffer as seen there.
|
||||
b := make([]byte, 0, len(tf.layout)+10)
|
||||
b = tf.time.AppendFormat(b, tf.layout)
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// Caller returns a Valuer that returns a file and line from a specified depth
|
||||
// in the callstack. Users will probably want to use DefaultCaller.
|
||||
func Caller(depth int) Valuer {
|
||||
return func() interface{} {
|
||||
_, file, line, _ := runtime.Caller(depth)
|
||||
idx := strings.LastIndexByte(file, '/')
|
||||
// using idx+1 below handles both of following cases:
|
||||
// idx == -1 because no "/" was found, or
|
||||
// idx >= 0 and we want to start at the character after the found "/".
|
||||
return file[idx+1:] + ":" + strconv.Itoa(line)
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
// DefaultTimestamp is a Valuer that returns the current wallclock time,
|
||||
// respecting time zones, when bound.
|
||||
DefaultTimestamp = TimestampFormat(time.Now, time.RFC3339Nano)
|
||||
|
||||
// DefaultTimestampUTC is a Valuer that returns the current time in UTC
|
||||
// when bound.
|
||||
DefaultTimestampUTC = TimestampFormat(
|
||||
func() time.Time { return time.Now().UTC() },
|
||||
time.RFC3339Nano,
|
||||
)
|
||||
|
||||
// DefaultCaller is a Valuer that returns the file and line where the Log
|
||||
// method was invoked. It can only be used with log.With.
|
||||
DefaultCaller = Caller(3)
|
||||
)
|
||||
1
vendor/github.com/go-logfmt/logfmt/.gitignore
generated
vendored
Normal file
1
vendor/github.com/go-logfmt/logfmt/.gitignore
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.vscode/
|
||||
48
vendor/github.com/go-logfmt/logfmt/CHANGELOG.md
generated
vendored
Normal file
48
vendor/github.com/go-logfmt/logfmt/CHANGELOG.md
generated
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
# Changelog
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [0.5.0] - 2020-01-03
|
||||
|
||||
### Changed
|
||||
- Remove the dependency on github.com/kr/logfmt by [@ChrisHines]
|
||||
- Move fuzz code to github.com/go-logfmt/fuzzlogfmt by [@ChrisHines]
|
||||
|
||||
## [0.4.0] - 2018-11-21
|
||||
|
||||
### Added
|
||||
- Go module support by [@ChrisHines]
|
||||
- CHANGELOG by [@ChrisHines]
|
||||
|
||||
### Changed
|
||||
- Drop invalid runes from keys instead of returning ErrInvalidKey by [@ChrisHines]
|
||||
- On panic while printing, attempt to print panic value by [@bboreham]
|
||||
|
||||
## [0.3.0] - 2016-11-15
|
||||
### Added
|
||||
- Pool buffers for quoted strings and byte slices by [@nussjustin]
|
||||
### Fixed
|
||||
- Fuzz fix, quote invalid UTF-8 values by [@judwhite]
|
||||
|
||||
## [0.2.0] - 2016-05-08
|
||||
### Added
|
||||
- Encoder.EncodeKeyvals by [@ChrisHines]
|
||||
|
||||
## [0.1.0] - 2016-03-28
|
||||
### Added
|
||||
- Encoder by [@ChrisHines]
|
||||
- Decoder by [@ChrisHines]
|
||||
- MarshalKeyvals by [@ChrisHines]
|
||||
|
||||
[0.5.0]: https://github.com/go-logfmt/logfmt/compare/v0.4.0...v0.5.0
|
||||
[0.4.0]: https://github.com/go-logfmt/logfmt/compare/v0.3.0...v0.4.0
|
||||
[0.3.0]: https://github.com/go-logfmt/logfmt/compare/v0.2.0...v0.3.0
|
||||
[0.2.0]: https://github.com/go-logfmt/logfmt/compare/v0.1.0...v0.2.0
|
||||
[0.1.0]: https://github.com/go-logfmt/logfmt/commits/v0.1.0
|
||||
|
||||
[@ChrisHines]: https://github.com/ChrisHines
|
||||
[@bboreham]: https://github.com/bboreham
|
||||
[@judwhite]: https://github.com/judwhite
|
||||
[@nussjustin]: https://github.com/nussjustin
|
||||
22
vendor/github.com/go-logfmt/logfmt/LICENSE
generated
vendored
Normal file
22
vendor/github.com/go-logfmt/logfmt/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 go-logfmt
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
33
vendor/github.com/go-logfmt/logfmt/README.md
generated
vendored
Normal file
33
vendor/github.com/go-logfmt/logfmt/README.md
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
[](https://pkg.go.dev/github.com/go-logfmt/logfmt)
|
||||
[](https://goreportcard.com/report/go-logfmt/logfmt)
|
||||
[](https://github.com/go-logfmt/logfmt/actions/workflows/test.yml)
|
||||
[](https://coveralls.io/github/go-logfmt/logfmt?branch=master)
|
||||
|
||||
# logfmt
|
||||
|
||||
Package logfmt implements utilities to marshal and unmarshal data in the [logfmt
|
||||
format](https://brandur.org/logfmt). It provides an API similar to
|
||||
[encoding/json](http://golang.org/pkg/encoding/json/) and
|
||||
[encoding/xml](http://golang.org/pkg/encoding/xml/).
|
||||
|
||||
The logfmt format was first documented by Brandur Leach in [this
|
||||
article](https://brandur.org/logfmt). The format has not been formally
|
||||
standardized. The most authoritative public specification to date has been the
|
||||
documentation of a Go Language [package](http://godoc.org/github.com/kr/logfmt)
|
||||
written by Blake Mizerany and Keith Rarick.
|
||||
|
||||
## Goals
|
||||
|
||||
This project attempts to conform as closely as possible to the prior art, while
|
||||
also removing ambiguity where necessary to provide well behaved encoder and
|
||||
decoder implementations.
|
||||
|
||||
## Non-goals
|
||||
|
||||
This project does not attempt to formally standardize the logfmt format. In the
|
||||
event that logfmt is standardized this project would take conforming to the
|
||||
standard as a goal.
|
||||
|
||||
## Versioning
|
||||
|
||||
Package logfmt publishes releases via [semver](http://semver.org/) compatible Git tags prefixed with a single 'v'.
|
||||
237
vendor/github.com/go-logfmt/logfmt/decode.go
generated
vendored
Normal file
237
vendor/github.com/go-logfmt/logfmt/decode.go
generated
vendored
Normal file
@@ -0,0 +1,237 @@
|
||||
package logfmt
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// A Decoder reads and decodes logfmt records from an input stream.
|
||||
type Decoder struct {
|
||||
pos int
|
||||
key []byte
|
||||
value []byte
|
||||
lineNum int
|
||||
s *bufio.Scanner
|
||||
err error
|
||||
}
|
||||
|
||||
// NewDecoder returns a new decoder that reads from r.
|
||||
//
|
||||
// The decoder introduces its own buffering and may read data from r beyond
|
||||
// the logfmt records requested.
|
||||
func NewDecoder(r io.Reader) *Decoder {
|
||||
dec := &Decoder{
|
||||
s: bufio.NewScanner(r),
|
||||
}
|
||||
return dec
|
||||
}
|
||||
|
||||
// ScanRecord advances the Decoder to the next record, which can then be
|
||||
// parsed with the ScanKeyval method. It returns false when decoding stops,
|
||||
// either by reaching the end of the input or an error. After ScanRecord
|
||||
// returns false, the Err method will return any error that occurred during
|
||||
// decoding, except that if it was io.EOF, Err will return nil.
|
||||
func (dec *Decoder) ScanRecord() bool {
|
||||
if dec.err != nil {
|
||||
return false
|
||||
}
|
||||
if !dec.s.Scan() {
|
||||
dec.err = dec.s.Err()
|
||||
return false
|
||||
}
|
||||
dec.lineNum++
|
||||
dec.pos = 0
|
||||
return true
|
||||
}
|
||||
|
||||
// ScanKeyval advances the Decoder to the next key/value pair of the current
|
||||
// record, which can then be retrieved with the Key and Value methods. It
|
||||
// returns false when decoding stops, either by reaching the end of the
|
||||
// current record or an error.
|
||||
func (dec *Decoder) ScanKeyval() bool {
|
||||
dec.key, dec.value = nil, nil
|
||||
if dec.err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
line := dec.s.Bytes()
|
||||
|
||||
// garbage
|
||||
for p, c := range line[dec.pos:] {
|
||||
if c > ' ' {
|
||||
dec.pos += p
|
||||
goto key
|
||||
}
|
||||
}
|
||||
dec.pos = len(line)
|
||||
return false
|
||||
|
||||
key:
|
||||
const invalidKeyError = "invalid key"
|
||||
|
||||
start, multibyte := dec.pos, false
|
||||
for p, c := range line[dec.pos:] {
|
||||
switch {
|
||||
case c == '=':
|
||||
dec.pos += p
|
||||
if dec.pos > start {
|
||||
dec.key = line[start:dec.pos]
|
||||
if multibyte && bytes.ContainsRune(dec.key, utf8.RuneError) {
|
||||
dec.syntaxError(invalidKeyError)
|
||||
return false
|
||||
}
|
||||
}
|
||||
if dec.key == nil {
|
||||
dec.unexpectedByte(c)
|
||||
return false
|
||||
}
|
||||
goto equal
|
||||
case c == '"':
|
||||
dec.pos += p
|
||||
dec.unexpectedByte(c)
|
||||
return false
|
||||
case c <= ' ':
|
||||
dec.pos += p
|
||||
if dec.pos > start {
|
||||
dec.key = line[start:dec.pos]
|
||||
if multibyte && bytes.ContainsRune(dec.key, utf8.RuneError) {
|
||||
dec.syntaxError(invalidKeyError)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
case c >= utf8.RuneSelf:
|
||||
multibyte = true
|
||||
}
|
||||
}
|
||||
dec.pos = len(line)
|
||||
if dec.pos > start {
|
||||
dec.key = line[start:dec.pos]
|
||||
if multibyte && bytes.ContainsRune(dec.key, utf8.RuneError) {
|
||||
dec.syntaxError(invalidKeyError)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
|
||||
equal:
|
||||
dec.pos++
|
||||
if dec.pos >= len(line) {
|
||||
return true
|
||||
}
|
||||
switch c := line[dec.pos]; {
|
||||
case c <= ' ':
|
||||
return true
|
||||
case c == '"':
|
||||
goto qvalue
|
||||
}
|
||||
|
||||
// value
|
||||
start = dec.pos
|
||||
for p, c := range line[dec.pos:] {
|
||||
switch {
|
||||
case c == '=' || c == '"':
|
||||
dec.pos += p
|
||||
dec.unexpectedByte(c)
|
||||
return false
|
||||
case c <= ' ':
|
||||
dec.pos += p
|
||||
if dec.pos > start {
|
||||
dec.value = line[start:dec.pos]
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
dec.pos = len(line)
|
||||
if dec.pos > start {
|
||||
dec.value = line[start:dec.pos]
|
||||
}
|
||||
return true
|
||||
|
||||
qvalue:
|
||||
const (
|
||||
untermQuote = "unterminated quoted value"
|
||||
invalidQuote = "invalid quoted value"
|
||||
)
|
||||
|
||||
hasEsc, esc := false, false
|
||||
start = dec.pos
|
||||
for p, c := range line[dec.pos+1:] {
|
||||
switch {
|
||||
case esc:
|
||||
esc = false
|
||||
case c == '\\':
|
||||
hasEsc, esc = true, true
|
||||
case c == '"':
|
||||
dec.pos += p + 2
|
||||
if hasEsc {
|
||||
v, ok := unquoteBytes(line[start:dec.pos])
|
||||
if !ok {
|
||||
dec.syntaxError(invalidQuote)
|
||||
return false
|
||||
}
|
||||
dec.value = v
|
||||
} else {
|
||||
start++
|
||||
end := dec.pos - 1
|
||||
if end > start {
|
||||
dec.value = line[start:end]
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
dec.pos = len(line)
|
||||
dec.syntaxError(untermQuote)
|
||||
return false
|
||||
}
|
||||
|
||||
// Key returns the most recent key found by a call to ScanKeyval. The returned
|
||||
// slice may point to internal buffers and is only valid until the next call
|
||||
// to ScanRecord. It does no allocation.
|
||||
func (dec *Decoder) Key() []byte {
|
||||
return dec.key
|
||||
}
|
||||
|
||||
// Value returns the most recent value found by a call to ScanKeyval. The
|
||||
// returned slice may point to internal buffers and is only valid until the
|
||||
// next call to ScanRecord. It does no allocation when the value has no
|
||||
// escape sequences.
|
||||
func (dec *Decoder) Value() []byte {
|
||||
return dec.value
|
||||
}
|
||||
|
||||
// Err returns the first non-EOF error that was encountered by the Scanner.
|
||||
func (dec *Decoder) Err() error {
|
||||
return dec.err
|
||||
}
|
||||
|
||||
func (dec *Decoder) syntaxError(msg string) {
|
||||
dec.err = &SyntaxError{
|
||||
Msg: msg,
|
||||
Line: dec.lineNum,
|
||||
Pos: dec.pos + 1,
|
||||
}
|
||||
}
|
||||
|
||||
func (dec *Decoder) unexpectedByte(c byte) {
|
||||
dec.err = &SyntaxError{
|
||||
Msg: fmt.Sprintf("unexpected %q", c),
|
||||
Line: dec.lineNum,
|
||||
Pos: dec.pos + 1,
|
||||
}
|
||||
}
|
||||
|
||||
// A SyntaxError represents a syntax error in the logfmt input stream.
|
||||
type SyntaxError struct {
|
||||
Msg string
|
||||
Line int
|
||||
Pos int
|
||||
}
|
||||
|
||||
func (e *SyntaxError) Error() string {
|
||||
return fmt.Sprintf("logfmt syntax error at pos %d on line %d: %s", e.Pos, e.Line, e.Msg)
|
||||
}
|
||||
6
vendor/github.com/go-logfmt/logfmt/doc.go
generated
vendored
Normal file
6
vendor/github.com/go-logfmt/logfmt/doc.go
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
// Package logfmt implements utilities to marshal and unmarshal data in the
|
||||
// logfmt format. The logfmt format records key/value pairs in a way that
|
||||
// balances readability for humans and simplicity of computer parsing. It is
|
||||
// most commonly used as a more human friendly alternative to JSON for
|
||||
// structured logging.
|
||||
package logfmt
|
||||
322
vendor/github.com/go-logfmt/logfmt/encode.go
generated
vendored
Normal file
322
vendor/github.com/go-logfmt/logfmt/encode.go
generated
vendored
Normal file
@@ -0,0 +1,322 @@
|
||||
package logfmt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// MarshalKeyvals returns the logfmt encoding of keyvals, a variadic sequence
|
||||
// of alternating keys and values.
|
||||
func MarshalKeyvals(keyvals ...interface{}) ([]byte, error) {
|
||||
buf := &bytes.Buffer{}
|
||||
if err := NewEncoder(buf).EncodeKeyvals(keyvals...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// An Encoder writes logfmt data to an output stream.
|
||||
type Encoder struct {
|
||||
w io.Writer
|
||||
scratch bytes.Buffer
|
||||
needSep bool
|
||||
}
|
||||
|
||||
// NewEncoder returns a new encoder that writes to w.
|
||||
func NewEncoder(w io.Writer) *Encoder {
|
||||
return &Encoder{
|
||||
w: w,
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
space = []byte(" ")
|
||||
equals = []byte("=")
|
||||
newline = []byte("\n")
|
||||
null = []byte("null")
|
||||
)
|
||||
|
||||
// EncodeKeyval writes the logfmt encoding of key and value to the stream. A
|
||||
// single space is written before the second and subsequent keys in a record.
|
||||
// Nothing is written if a non-nil error is returned.
|
||||
func (enc *Encoder) EncodeKeyval(key, value interface{}) error {
|
||||
enc.scratch.Reset()
|
||||
if enc.needSep {
|
||||
if _, err := enc.scratch.Write(space); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := writeKey(&enc.scratch, key); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := enc.scratch.Write(equals); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := writeValue(&enc.scratch, value); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err := enc.w.Write(enc.scratch.Bytes())
|
||||
enc.needSep = true
|
||||
return err
|
||||
}
|
||||
|
||||
// EncodeKeyvals writes the logfmt encoding of keyvals to the stream. Keyvals
|
||||
// is a variadic sequence of alternating keys and values. Keys of unsupported
|
||||
// type are skipped along with their corresponding value. Values of
|
||||
// unsupported type or that cause a MarshalerError are replaced by their error
|
||||
// but do not cause EncodeKeyvals to return an error. If a non-nil error is
|
||||
// returned some key/value pairs may not have be written.
|
||||
func (enc *Encoder) EncodeKeyvals(keyvals ...interface{}) error {
|
||||
if len(keyvals) == 0 {
|
||||
return nil
|
||||
}
|
||||
if len(keyvals)%2 == 1 {
|
||||
keyvals = append(keyvals, nil)
|
||||
}
|
||||
for i := 0; i < len(keyvals); i += 2 {
|
||||
k, v := keyvals[i], keyvals[i+1]
|
||||
err := enc.EncodeKeyval(k, v)
|
||||
if err == ErrUnsupportedKeyType {
|
||||
continue
|
||||
}
|
||||
if _, ok := err.(*MarshalerError); ok || err == ErrUnsupportedValueType {
|
||||
v = err
|
||||
err = enc.EncodeKeyval(k, v)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalerError represents an error encountered while marshaling a value.
|
||||
type MarshalerError struct {
|
||||
Type reflect.Type
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *MarshalerError) Error() string {
|
||||
return "error marshaling value of type " + e.Type.String() + ": " + e.Err.Error()
|
||||
}
|
||||
|
||||
// ErrNilKey is returned by Marshal functions and Encoder methods if a key is
|
||||
// a nil interface or pointer value.
|
||||
var ErrNilKey = errors.New("nil key")
|
||||
|
||||
// ErrInvalidKey is returned by Marshal functions and Encoder methods if, after
|
||||
// dropping invalid runes, a key is empty.
|
||||
var ErrInvalidKey = errors.New("invalid key")
|
||||
|
||||
// ErrUnsupportedKeyType is returned by Encoder methods if a key has an
|
||||
// unsupported type.
|
||||
var ErrUnsupportedKeyType = errors.New("unsupported key type")
|
||||
|
||||
// ErrUnsupportedValueType is returned by Encoder methods if a value has an
|
||||
// unsupported type.
|
||||
var ErrUnsupportedValueType = errors.New("unsupported value type")
|
||||
|
||||
func writeKey(w io.Writer, key interface{}) error {
|
||||
if key == nil {
|
||||
return ErrNilKey
|
||||
}
|
||||
|
||||
switch k := key.(type) {
|
||||
case string:
|
||||
return writeStringKey(w, k)
|
||||
case []byte:
|
||||
if k == nil {
|
||||
return ErrNilKey
|
||||
}
|
||||
return writeBytesKey(w, k)
|
||||
case encoding.TextMarshaler:
|
||||
kb, err := safeMarshal(k)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if kb == nil {
|
||||
return ErrNilKey
|
||||
}
|
||||
return writeBytesKey(w, kb)
|
||||
case fmt.Stringer:
|
||||
ks, ok := safeString(k)
|
||||
if !ok {
|
||||
return ErrNilKey
|
||||
}
|
||||
return writeStringKey(w, ks)
|
||||
default:
|
||||
rkey := reflect.ValueOf(key)
|
||||
switch rkey.Kind() {
|
||||
case reflect.Array, reflect.Chan, reflect.Func, reflect.Map, reflect.Slice, reflect.Struct:
|
||||
return ErrUnsupportedKeyType
|
||||
case reflect.Ptr:
|
||||
if rkey.IsNil() {
|
||||
return ErrNilKey
|
||||
}
|
||||
return writeKey(w, rkey.Elem().Interface())
|
||||
}
|
||||
return writeStringKey(w, fmt.Sprint(k))
|
||||
}
|
||||
}
|
||||
|
||||
// keyRuneFilter returns r for all valid key runes, and -1 for all invalid key
|
||||
// runes. When used as the mapping function for strings.Map and bytes.Map
|
||||
// functions it causes them to remove invalid key runes from strings or byte
|
||||
// slices respectively.
|
||||
func keyRuneFilter(r rune) rune {
|
||||
if r <= ' ' || r == '=' || r == '"' || r == utf8.RuneError {
|
||||
return -1
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func writeStringKey(w io.Writer, key string) error {
|
||||
k := strings.Map(keyRuneFilter, key)
|
||||
if k == "" {
|
||||
return ErrInvalidKey
|
||||
}
|
||||
_, err := io.WriteString(w, k)
|
||||
return err
|
||||
}
|
||||
|
||||
func writeBytesKey(w io.Writer, key []byte) error {
|
||||
k := bytes.Map(keyRuneFilter, key)
|
||||
if len(k) == 0 {
|
||||
return ErrInvalidKey
|
||||
}
|
||||
_, err := w.Write(k)
|
||||
return err
|
||||
}
|
||||
|
||||
func writeValue(w io.Writer, value interface{}) error {
|
||||
switch v := value.(type) {
|
||||
case nil:
|
||||
return writeBytesValue(w, null)
|
||||
case string:
|
||||
return writeStringValue(w, v, true)
|
||||
case []byte:
|
||||
return writeBytesValue(w, v)
|
||||
case encoding.TextMarshaler:
|
||||
vb, err := safeMarshal(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if vb == nil {
|
||||
vb = null
|
||||
}
|
||||
return writeBytesValue(w, vb)
|
||||
case error:
|
||||
se, ok := safeError(v)
|
||||
return writeStringValue(w, se, ok)
|
||||
case fmt.Stringer:
|
||||
ss, ok := safeString(v)
|
||||
return writeStringValue(w, ss, ok)
|
||||
default:
|
||||
rvalue := reflect.ValueOf(value)
|
||||
switch rvalue.Kind() {
|
||||
case reflect.Array, reflect.Chan, reflect.Func, reflect.Map, reflect.Slice, reflect.Struct:
|
||||
return ErrUnsupportedValueType
|
||||
case reflect.Ptr:
|
||||
if rvalue.IsNil() {
|
||||
return writeBytesValue(w, null)
|
||||
}
|
||||
return writeValue(w, rvalue.Elem().Interface())
|
||||
}
|
||||
return writeStringValue(w, fmt.Sprint(v), true)
|
||||
}
|
||||
}
|
||||
|
||||
func needsQuotedValueRune(r rune) bool {
|
||||
return r <= ' ' || r == '=' || r == '"' || r == utf8.RuneError
|
||||
}
|
||||
|
||||
func writeStringValue(w io.Writer, value string, ok bool) error {
|
||||
var err error
|
||||
if ok && value == "null" {
|
||||
_, err = io.WriteString(w, `"null"`)
|
||||
} else if strings.IndexFunc(value, needsQuotedValueRune) != -1 {
|
||||
_, err = writeQuotedString(w, value)
|
||||
} else {
|
||||
_, err = io.WriteString(w, value)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func writeBytesValue(w io.Writer, value []byte) error {
|
||||
var err error
|
||||
if bytes.IndexFunc(value, needsQuotedValueRune) != -1 {
|
||||
_, err = writeQuotedBytes(w, value)
|
||||
} else {
|
||||
_, err = w.Write(value)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// EndRecord writes a newline character to the stream and resets the encoder
|
||||
// to the beginning of a new record.
|
||||
func (enc *Encoder) EndRecord() error {
|
||||
_, err := enc.w.Write(newline)
|
||||
if err == nil {
|
||||
enc.needSep = false
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Reset resets the encoder to the beginning of a new record.
|
||||
func (enc *Encoder) Reset() {
|
||||
enc.needSep = false
|
||||
}
|
||||
|
||||
func safeError(err error) (s string, ok bool) {
|
||||
defer func() {
|
||||
if panicVal := recover(); panicVal != nil {
|
||||
if v := reflect.ValueOf(err); v.Kind() == reflect.Ptr && v.IsNil() {
|
||||
s, ok = "null", false
|
||||
} else {
|
||||
s, ok = fmt.Sprintf("PANIC:%v", panicVal), false
|
||||
}
|
||||
}
|
||||
}()
|
||||
s, ok = err.Error(), true
|
||||
return
|
||||
}
|
||||
|
||||
func safeString(str fmt.Stringer) (s string, ok bool) {
|
||||
defer func() {
|
||||
if panicVal := recover(); panicVal != nil {
|
||||
if v := reflect.ValueOf(str); v.Kind() == reflect.Ptr && v.IsNil() {
|
||||
s, ok = "null", false
|
||||
} else {
|
||||
s, ok = fmt.Sprintf("PANIC:%v", panicVal), true
|
||||
}
|
||||
}
|
||||
}()
|
||||
s, ok = str.String(), true
|
||||
return
|
||||
}
|
||||
|
||||
func safeMarshal(tm encoding.TextMarshaler) (b []byte, err error) {
|
||||
defer func() {
|
||||
if panicVal := recover(); panicVal != nil {
|
||||
if v := reflect.ValueOf(tm); v.Kind() == reflect.Ptr && v.IsNil() {
|
||||
b, err = nil, nil
|
||||
} else {
|
||||
b, err = nil, fmt.Errorf("panic when marshalling: %s", panicVal)
|
||||
}
|
||||
}
|
||||
}()
|
||||
b, err = tm.MarshalText()
|
||||
if err != nil {
|
||||
return nil, &MarshalerError{
|
||||
Type: reflect.TypeOf(tm),
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
277
vendor/github.com/go-logfmt/logfmt/jsonstring.go
generated
vendored
Normal file
277
vendor/github.com/go-logfmt/logfmt/jsonstring.go
generated
vendored
Normal file
@@ -0,0 +1,277 @@
|
||||
package logfmt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"strconv"
|
||||
"sync"
|
||||
"unicode"
|
||||
"unicode/utf16"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// Taken from Go's encoding/json and modified for use here.
|
||||
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
var hex = "0123456789abcdef"
|
||||
|
||||
var bufferPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
return &bytes.Buffer{}
|
||||
},
|
||||
}
|
||||
|
||||
func getBuffer() *bytes.Buffer {
|
||||
return bufferPool.Get().(*bytes.Buffer)
|
||||
}
|
||||
|
||||
func poolBuffer(buf *bytes.Buffer) {
|
||||
buf.Reset()
|
||||
bufferPool.Put(buf)
|
||||
}
|
||||
|
||||
// NOTE: keep in sync with writeQuotedBytes below.
|
||||
func writeQuotedString(w io.Writer, s string) (int, error) {
|
||||
buf := getBuffer()
|
||||
buf.WriteByte('"')
|
||||
start := 0
|
||||
for i := 0; i < len(s); {
|
||||
if b := s[i]; b < utf8.RuneSelf {
|
||||
if 0x20 <= b && b != '\\' && b != '"' {
|
||||
i++
|
||||
continue
|
||||
}
|
||||
if start < i {
|
||||
buf.WriteString(s[start:i])
|
||||
}
|
||||
switch b {
|
||||
case '\\', '"':
|
||||
buf.WriteByte('\\')
|
||||
buf.WriteByte(b)
|
||||
case '\n':
|
||||
buf.WriteByte('\\')
|
||||
buf.WriteByte('n')
|
||||
case '\r':
|
||||
buf.WriteByte('\\')
|
||||
buf.WriteByte('r')
|
||||
case '\t':
|
||||
buf.WriteByte('\\')
|
||||
buf.WriteByte('t')
|
||||
default:
|
||||
// This encodes bytes < 0x20 except for \n, \r, and \t.
|
||||
buf.WriteString(`\u00`)
|
||||
buf.WriteByte(hex[b>>4])
|
||||
buf.WriteByte(hex[b&0xF])
|
||||
}
|
||||
i++
|
||||
start = i
|
||||
continue
|
||||
}
|
||||
c, size := utf8.DecodeRuneInString(s[i:])
|
||||
if c == utf8.RuneError {
|
||||
if start < i {
|
||||
buf.WriteString(s[start:i])
|
||||
}
|
||||
buf.WriteString(`\ufffd`)
|
||||
i += size
|
||||
start = i
|
||||
continue
|
||||
}
|
||||
i += size
|
||||
}
|
||||
if start < len(s) {
|
||||
buf.WriteString(s[start:])
|
||||
}
|
||||
buf.WriteByte('"')
|
||||
n, err := w.Write(buf.Bytes())
|
||||
poolBuffer(buf)
|
||||
return n, err
|
||||
}
|
||||
|
||||
// NOTE: keep in sync with writeQuoteString above.
|
||||
func writeQuotedBytes(w io.Writer, s []byte) (int, error) {
|
||||
buf := getBuffer()
|
||||
buf.WriteByte('"')
|
||||
start := 0
|
||||
for i := 0; i < len(s); {
|
||||
if b := s[i]; b < utf8.RuneSelf {
|
||||
if 0x20 <= b && b != '\\' && b != '"' {
|
||||
i++
|
||||
continue
|
||||
}
|
||||
if start < i {
|
||||
buf.Write(s[start:i])
|
||||
}
|
||||
switch b {
|
||||
case '\\', '"':
|
||||
buf.WriteByte('\\')
|
||||
buf.WriteByte(b)
|
||||
case '\n':
|
||||
buf.WriteByte('\\')
|
||||
buf.WriteByte('n')
|
||||
case '\r':
|
||||
buf.WriteByte('\\')
|
||||
buf.WriteByte('r')
|
||||
case '\t':
|
||||
buf.WriteByte('\\')
|
||||
buf.WriteByte('t')
|
||||
default:
|
||||
// This encodes bytes < 0x20 except for \n, \r, and \t.
|
||||
buf.WriteString(`\u00`)
|
||||
buf.WriteByte(hex[b>>4])
|
||||
buf.WriteByte(hex[b&0xF])
|
||||
}
|
||||
i++
|
||||
start = i
|
||||
continue
|
||||
}
|
||||
c, size := utf8.DecodeRune(s[i:])
|
||||
if c == utf8.RuneError {
|
||||
if start < i {
|
||||
buf.Write(s[start:i])
|
||||
}
|
||||
buf.WriteString(`\ufffd`)
|
||||
i += size
|
||||
start = i
|
||||
continue
|
||||
}
|
||||
i += size
|
||||
}
|
||||
if start < len(s) {
|
||||
buf.Write(s[start:])
|
||||
}
|
||||
buf.WriteByte('"')
|
||||
n, err := w.Write(buf.Bytes())
|
||||
poolBuffer(buf)
|
||||
return n, err
|
||||
}
|
||||
|
||||
// getu4 decodes \uXXXX from the beginning of s, returning the hex value,
|
||||
// or it returns -1.
|
||||
func getu4(s []byte) rune {
|
||||
if len(s) < 6 || s[0] != '\\' || s[1] != 'u' {
|
||||
return -1
|
||||
}
|
||||
r, err := strconv.ParseUint(string(s[2:6]), 16, 64)
|
||||
if err != nil {
|
||||
return -1
|
||||
}
|
||||
return rune(r)
|
||||
}
|
||||
|
||||
func unquoteBytes(s []byte) (t []byte, ok bool) {
|
||||
if len(s) < 2 || s[0] != '"' || s[len(s)-1] != '"' {
|
||||
return
|
||||
}
|
||||
s = s[1 : len(s)-1]
|
||||
|
||||
// Check for unusual characters. If there are none,
|
||||
// then no unquoting is needed, so return a slice of the
|
||||
// original bytes.
|
||||
r := 0
|
||||
for r < len(s) {
|
||||
c := s[r]
|
||||
if c == '\\' || c == '"' || c < ' ' {
|
||||
break
|
||||
}
|
||||
if c < utf8.RuneSelf {
|
||||
r++
|
||||
continue
|
||||
}
|
||||
rr, size := utf8.DecodeRune(s[r:])
|
||||
if rr == utf8.RuneError {
|
||||
break
|
||||
}
|
||||
r += size
|
||||
}
|
||||
if r == len(s) {
|
||||
return s, true
|
||||
}
|
||||
|
||||
b := make([]byte, len(s)+2*utf8.UTFMax)
|
||||
w := copy(b, s[0:r])
|
||||
for r < len(s) {
|
||||
// Out of room? Can only happen if s is full of
|
||||
// malformed UTF-8 and we're replacing each
|
||||
// byte with RuneError.
|
||||
if w >= len(b)-2*utf8.UTFMax {
|
||||
nb := make([]byte, (len(b)+utf8.UTFMax)*2)
|
||||
copy(nb, b[0:w])
|
||||
b = nb
|
||||
}
|
||||
switch c := s[r]; {
|
||||
case c == '\\':
|
||||
r++
|
||||
if r >= len(s) {
|
||||
return
|
||||
}
|
||||
switch s[r] {
|
||||
default:
|
||||
return
|
||||
case '"', '\\', '/', '\'':
|
||||
b[w] = s[r]
|
||||
r++
|
||||
w++
|
||||
case 'b':
|
||||
b[w] = '\b'
|
||||
r++
|
||||
w++
|
||||
case 'f':
|
||||
b[w] = '\f'
|
||||
r++
|
||||
w++
|
||||
case 'n':
|
||||
b[w] = '\n'
|
||||
r++
|
||||
w++
|
||||
case 'r':
|
||||
b[w] = '\r'
|
||||
r++
|
||||
w++
|
||||
case 't':
|
||||
b[w] = '\t'
|
||||
r++
|
||||
w++
|
||||
case 'u':
|
||||
r--
|
||||
rr := getu4(s[r:])
|
||||
if rr < 0 {
|
||||
return
|
||||
}
|
||||
r += 6
|
||||
if utf16.IsSurrogate(rr) {
|
||||
rr1 := getu4(s[r:])
|
||||
if dec := utf16.DecodeRune(rr, rr1); dec != unicode.ReplacementChar {
|
||||
// A valid pair; consume.
|
||||
r += 6
|
||||
w += utf8.EncodeRune(b[w:], dec)
|
||||
break
|
||||
}
|
||||
// Invalid surrogate; fall back to replacement rune.
|
||||
rr = unicode.ReplacementChar
|
||||
}
|
||||
w += utf8.EncodeRune(b[w:], rr)
|
||||
}
|
||||
|
||||
// Quote, control characters are invalid.
|
||||
case c == '"', c < ' ':
|
||||
return
|
||||
|
||||
// ASCII
|
||||
case c < utf8.RuneSelf:
|
||||
b[w] = c
|
||||
r++
|
||||
w++
|
||||
|
||||
// Coerce to well-formed UTF-8.
|
||||
default:
|
||||
rr, size := utf8.DecodeRune(s[r:])
|
||||
r += size
|
||||
w += utf8.EncodeRune(b[w:], rr)
|
||||
}
|
||||
}
|
||||
return b[0:w], true
|
||||
}
|
||||
6
vendor/modules.txt
vendored
6
vendor/modules.txt
vendored
@@ -417,6 +417,12 @@ github.com/fatih/color
|
||||
# github.com/felixge/httpsnoop v1.0.4
|
||||
## explicit; go 1.13
|
||||
github.com/felixge/httpsnoop
|
||||
# github.com/go-kit/kit v0.9.0
|
||||
## explicit
|
||||
github.com/go-kit/kit/log
|
||||
# github.com/go-logfmt/logfmt v0.5.1
|
||||
## explicit; go 1.17
|
||||
github.com/go-logfmt/logfmt
|
||||
# github.com/go-logr/logr v1.4.2
|
||||
## explicit; go 1.18
|
||||
github.com/go-logr/logr
|
||||
|
||||
Reference in New Issue
Block a user