update promlog to latest version (#4876)

* update promlog to latest version

Signed-off-by: Alex Yu <yu.alex96@gmail.com>

* Update api tests, fix main setup

Signed-off-by: Alex Yu <yu.alex96@gmail.com>

* tidy go.sum

Signed-off-by: Alex Yu <yu.alex96@gmail.com>

* revendor prometheus/common

Signed-off-by: Alex Yu <yu.alex96@gmail.com>

* only initialize config; use kingpin for remote_storage_adapter

Signed-off-by: Alex Yu <yu.alex96@gmail.com>

* actually parse the flags

Signed-off-by: Alex Yu <yu.alex96@gmail.com>

* clean up imports

Signed-off-by: Alex Yu <yu.alex96@gmail.com>
This commit is contained in:
Alex Yu 2018-11-23 08:22:40 -05:00 committed by Simon Pasquier
parent 996fd958ac
commit 5dcce32ef8
12 changed files with 385 additions and 165 deletions

View file

@ -101,11 +101,12 @@ func main() {
prometheusURL string prometheusURL string
logLevel promlog.AllowedLevel promlogConfig promlog.Config
}{ }{
notifier: notifier.Options{ notifier: notifier.Options{
Registerer: prometheus.DefaultRegisterer, Registerer: prometheus.DefaultRegisterer,
}, },
promlogConfig: promlog.Config{},
} }
a := kingpin.New(filepath.Base(os.Args[0]), "The Prometheus monitoring server") a := kingpin.New(filepath.Base(os.Args[0]), "The Prometheus monitoring server")
@ -204,7 +205,7 @@ func main() {
a.Flag("query.max-samples", "Maximum number of samples a single query can load into memory. Note that queries will fail if they would load more samples than this into memory, so this also limits the number of samples a query can return."). a.Flag("query.max-samples", "Maximum number of samples a single query can load into memory. Note that queries will fail if they would load more samples than this into memory, so this also limits the number of samples a query can return.").
Default("50000000").IntVar(&cfg.queryMaxSamples) Default("50000000").IntVar(&cfg.queryMaxSamples)
promlogflag.AddFlags(a, &cfg.logLevel) promlogflag.AddFlags(a, &cfg.promlogConfig)
_, err := a.Parse(os.Args[1:]) _, err := a.Parse(os.Args[1:])
if err != nil { if err != nil {
@ -233,7 +234,7 @@ func main() {
promql.LookbackDelta = time.Duration(cfg.lookbackDelta) promql.LookbackDelta = time.Duration(cfg.lookbackDelta)
logger := promlog.New(cfg.logLevel) logger := promlog.New(&cfg.promlogConfig)
// XXX(fabxc): Kubernetes does background logging which we can only customize by modifying // XXX(fabxc): Kubernetes does background logging which we can only customize by modifying
// a global variable. // a global variable.

View file

@ -15,13 +15,13 @@
package main package main
import ( import (
"flag"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
_ "net/http/pprof" _ "net/http/pprof"
"net/url" "net/url"
"os" "os"
"path/filepath"
"sync" "sync"
"time" "time"
@ -29,13 +29,17 @@ import (
"github.com/go-kit/kit/log/level" "github.com/go-kit/kit/log/level"
"github.com/gogo/protobuf/proto" "github.com/gogo/protobuf/proto"
"github.com/golang/snappy" "github.com/golang/snappy"
"github.com/pkg/errors"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp" "github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/prometheus/common/model" "github.com/prometheus/common/model"
"gopkg.in/alecthomas/kingpin.v2"
influx "github.com/influxdata/influxdb/client/v2" influx "github.com/influxdata/influxdb/client/v2"
"github.com/prometheus/common/promlog" "github.com/prometheus/common/promlog"
"github.com/prometheus/common/promlog/flag"
"github.com/prometheus/prometheus/documentation/examples/remote_storage/remote_storage_adapter/graphite" "github.com/prometheus/prometheus/documentation/examples/remote_storage/remote_storage_adapter/graphite"
"github.com/prometheus/prometheus/documentation/examples/remote_storage/remote_storage_adapter/influxdb" "github.com/prometheus/prometheus/documentation/examples/remote_storage/remote_storage_adapter/influxdb"
"github.com/prometheus/prometheus/documentation/examples/remote_storage/remote_storage_adapter/opentsdb" "github.com/prometheus/prometheus/documentation/examples/remote_storage/remote_storage_adapter/opentsdb"
@ -55,7 +59,7 @@ type config struct {
remoteTimeout time.Duration remoteTimeout time.Duration
listenAddr string listenAddr string
telemetryPath string telemetryPath string
logLevel string promlogConfig promlog.Config
} }
var ( var (
@ -100,11 +104,7 @@ func main() {
cfg := parseFlags() cfg := parseFlags()
http.Handle(cfg.telemetryPath, promhttp.Handler()) http.Handle(cfg.telemetryPath, promhttp.Handler())
logLevel := promlog.AllowedLevel{} logger := promlog.New(&cfg.promlogConfig)
if err := logLevel.Set(cfg.logLevel); err != nil {
panic(fmt.Sprintf("Error setting log level: %v", err))
}
logger := promlog.New(logLevel)
writers, readers := buildClients(logger, cfg) writers, readers := buildClients(logger, cfg)
if err := serve(logger, cfg.listenAddr, writers, readers); err != nil { if err := serve(logger, cfg.listenAddr, writers, readers); err != nil {
@ -114,42 +114,45 @@ func main() {
} }
func parseFlags() *config { func parseFlags() *config {
a := kingpin.New(filepath.Base(os.Args[0]), "Remote storage adapter")
a.HelpFlag.Short('h')
cfg := &config{ cfg := &config{
influxdbPassword: os.Getenv("INFLUXDB_PW"), influxdbPassword: os.Getenv("INFLUXDB_PW"),
promlogConfig: promlog.Config{},
} }
flag.StringVar(&cfg.graphiteAddress, "graphite-address", "", a.Flag("graphite-address", "The host:port of the Graphite server to send samples to. None, if empty.").
"The host:port of the Graphite server to send samples to. None, if empty.", Default("").StringVar(&cfg.graphiteAddress)
) a.Flag("graphite-transport", "Transport protocol to use to communicate with Graphite. 'tcp', if empty.").
flag.StringVar(&cfg.graphiteTransport, "graphite-transport", "tcp", Default("tcp").StringVar(&cfg.graphiteTransport)
"Transport protocol to use to communicate with Graphite. 'tcp', if empty.", a.Flag("graphite-prefix", "The prefix to prepend to all metrics exported to Graphite. None, if empty.").
) Default("").StringVar(&cfg.graphitePrefix)
flag.StringVar(&cfg.graphitePrefix, "graphite-prefix", "", a.Flag("opentsdb-url", "The URL of the remote OpenTSDB server to send samples to. None, if empty.").
"The prefix to prepend to all metrics exported to Graphite. None, if empty.", Default("").StringVar(&cfg.opentsdbURL)
) a.Flag("influxdb-url", "The URL of the remote InfluxDB server to send samples to. None, if empty.").
flag.StringVar(&cfg.opentsdbURL, "opentsdb-url", "", Default("").StringVar(&cfg.influxdbURL)
"The URL of the remote OpenTSDB server to send samples to. None, if empty.", a.Flag("influxdb.retention-policy", "The InfluxDB retention policy to use.").
) Default("autogen").StringVar(&cfg.influxdbRetentionPolicy)
flag.StringVar(&cfg.influxdbURL, "influxdb-url", "", a.Flag("influxdb.username", "The username to use when sending samples to InfluxDB. The corresponding password must be provided via the INFLUXDB_PW environment variable.").
"The URL of the remote InfluxDB server to send samples to. None, if empty.", Default("").StringVar(&cfg.influxdbUsername)
) a.Flag("influxdb.database", "The name of the database to use for storing samples in InfluxDB.").
flag.StringVar(&cfg.influxdbRetentionPolicy, "influxdb.retention-policy", "autogen", Default("prometheus").StringVar(&cfg.influxdbDatabase)
"The InfluxDB retention policy to use.", a.Flag("send-timeout", "The timeout to use when sending samples to the remote storage.").
) Default("30s").DurationVar(&cfg.remoteTimeout)
flag.StringVar(&cfg.influxdbUsername, "influxdb.username", "", a.Flag("web.listen-address", "Address to listen on for web endpoints.").
"The username to use when sending samples to InfluxDB. The corresponding password must be provided via the INFLUXDB_PW environment variable.", Default(":9201").StringVar(&cfg.listenAddr)
) a.Flag("web.telemetry-path", "Address to listen on for web endpoints.").
flag.StringVar(&cfg.influxdbDatabase, "influxdb.database", "prometheus", Default("/metrics").StringVar(&cfg.telemetryPath)
"The name of the database to use for storing samples in InfluxDB.",
)
flag.DurationVar(&cfg.remoteTimeout, "send-timeout", 30*time.Second,
"The timeout to use when sending samples to the remote storage.",
)
flag.StringVar(&cfg.listenAddr, "web.listen-address", ":9201", "Address to listen on for web endpoints.")
flag.StringVar(&cfg.telemetryPath, "web.telemetry-path", "/metrics", "Address to listen on for web endpoints.")
flag.StringVar(&cfg.logLevel, "log.level", "debug", "Only log messages with the given severity or above. One of: [debug, info, warn, error]")
flag.Parse() flag.AddFlags(a, &cfg.promlogConfig)
_, err := a.Parse(os.Args[1:])
if err != nil {
fmt.Fprintln(os.Stderr, errors.Wrapf(err, "Error parsing commandline arguments"))
a.Usage(os.Args[1:])
os.Exit(2)
}
return cfg return cfg
} }

2
go.mod
View file

@ -103,7 +103,7 @@ require (
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0 // indirect github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0 // indirect
github.com/prometheus/client_golang v0.0.0-20181001174001-0a8115f42e03 github.com/prometheus/client_golang v0.0.0-20181001174001-0a8115f42e03
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910
github.com/prometheus/common v0.0.0-20180518154759-7600349dcfe1 github.com/prometheus/common v0.0.0-20181119215939-b36ad289a3ea
github.com/prometheus/procfs v0.0.0-20160411190841-abf152e5f3e9 // indirect github.com/prometheus/procfs v0.0.0-20160411190841-abf152e5f3e9 // indirect
github.com/prometheus/tsdb v0.2.0 github.com/prometheus/tsdb v0.2.0
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a // indirect github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a // indirect

4
go.sum
View file

@ -211,8 +211,8 @@ github.com/prometheus/client_golang v0.0.0-20181001174001-0a8115f42e03 h1:716+Mw
github.com/prometheus/client_golang v0.0.0-20181001174001-0a8115f42e03/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.0.0-20181001174001-0a8115f42e03/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/common v0.0.0-20180518154759-7600349dcfe1 h1:osmNoEW2SCW3L7EX0km2LYM8HKpNWRiouxjE3XHkyGc= github.com/prometheus/common v0.0.0-20181119215939-b36ad289a3ea h1:4RkbEb5XX0Wvu3XhIW3zxgLUhUE9suNc7YLO52/RyT4=
github.com/prometheus/common v0.0.0-20180518154759-7600349dcfe1/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.0.0-20181119215939-b36ad289a3ea/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/procfs v0.0.0-20160411190841-abf152e5f3e9 h1:IrO4Eb9oGw+GxzOhO4b2QC5EWO85Omh/4iTSPZktMm8= github.com/prometheus/procfs v0.0.0-20160411190841-abf152e5f3e9 h1:IrO4Eb9oGw+GxzOhO4b2QC5EWO85Omh/4iTSPZktMm8=
github.com/prometheus/procfs v0.0.0-20160411190841-abf152e5f3e9/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20160411190841-abf152e5f3e9/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/tsdb v0.2.0 h1:27z98vFd/gPew17nmKEbLn37exGCwc2F5EyrgScg6bk= github.com/prometheus/tsdb v0.2.0 h1:27z98vFd/gPew17nmKEbLn37exGCwc2F5EyrgScg6bk=

View file

@ -14,13 +14,45 @@
package expfmt package expfmt
import ( import (
"bytes"
"fmt" "fmt"
"io" "io"
"math" "math"
"strconv"
"strings" "strings"
"sync"
"github.com/prometheus/common/model"
dto "github.com/prometheus/client_model/go" dto "github.com/prometheus/client_model/go"
"github.com/prometheus/common/model" )
// enhancedWriter has all the enhanced write functions needed here. bytes.Buffer
// implements it.
type enhancedWriter interface {
io.Writer
WriteRune(r rune) (n int, err error)
WriteString(s string) (n int, err error)
WriteByte(c byte) error
}
const (
initialBufSize = 512
initialNumBufSize = 24
)
var (
bufPool = sync.Pool{
New: func() interface{} {
return bytes.NewBuffer(make([]byte, 0, initialBufSize))
},
}
numBufPool = sync.Pool{
New: func() interface{} {
b := make([]byte, 0, initialNumBufSize)
return &b
},
}
) )
// MetricFamilyToText converts a MetricFamily proto message into text format and // MetricFamilyToText converts a MetricFamily proto message into text format and
@ -32,37 +64,92 @@ import (
// will result in invalid text format output. // will result in invalid text format output.
// //
// This method fulfills the type 'prometheus.encoder'. // This method fulfills the type 'prometheus.encoder'.
func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (int, error) { func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (written int, err error) {
var written int
// Fail-fast checks. // Fail-fast checks.
if len(in.Metric) == 0 { if len(in.Metric) == 0 {
return written, fmt.Errorf("MetricFamily has no metrics: %s", in) return 0, fmt.Errorf("MetricFamily has no metrics: %s", in)
} }
name := in.GetName() name := in.GetName()
if name == "" { if name == "" {
return written, fmt.Errorf("MetricFamily has no name: %s", in) return 0, fmt.Errorf("MetricFamily has no name: %s", in)
} }
// Try the interface upgrade. If it doesn't work, we'll use a
// bytes.Buffer from the sync.Pool and write out its content to out in a
// single go in the end.
w, ok := out.(enhancedWriter)
if !ok {
b := bufPool.Get().(*bytes.Buffer)
b.Reset()
w = b
defer func() {
bWritten, bErr := out.Write(b.Bytes())
written = bWritten
if err == nil {
err = bErr
}
bufPool.Put(b)
}()
}
var n int
// Comments, first HELP, then TYPE. // Comments, first HELP, then TYPE.
if in.Help != nil { if in.Help != nil {
n, err := fmt.Fprintf( n, err = w.WriteString("# HELP ")
out, "# HELP %s %s\n",
name, escapeString(*in.Help, false),
)
written += n written += n
if err != nil { if err != nil {
return written, err return
} }
n, err = w.WriteString(name)
written += n
if err != nil {
return
}
err = w.WriteByte(' ')
written++
if err != nil {
return
}
n, err = writeEscapedString(w, *in.Help, false)
written += n
if err != nil {
return
}
err = w.WriteByte('\n')
written++
if err != nil {
return
}
}
n, err = w.WriteString("# TYPE ")
written += n
if err != nil {
return
}
n, err = w.WriteString(name)
written += n
if err != nil {
return
} }
metricType := in.GetType() metricType := in.GetType()
n, err := fmt.Fprintf( switch metricType {
out, "# TYPE %s %s\n", case dto.MetricType_COUNTER:
name, strings.ToLower(metricType.String()), n, err = w.WriteString(" counter\n")
) case dto.MetricType_GAUGE:
n, err = w.WriteString(" gauge\n")
case dto.MetricType_SUMMARY:
n, err = w.WriteString(" summary\n")
case dto.MetricType_UNTYPED:
n, err = w.WriteString(" untyped\n")
case dto.MetricType_HISTOGRAM:
n, err = w.WriteString(" histogram\n")
default:
return written, fmt.Errorf("unknown metric type %s", metricType.String())
}
written += n written += n
if err != nil { if err != nil {
return written, err return
} }
// Finally the samples, one line for each. // Finally the samples, one line for each.
@ -75,9 +162,8 @@ func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (int, error) {
) )
} }
n, err = writeSample( n, err = writeSample(
name, metric, "", "", w, name, "", metric, "", 0,
metric.Counter.GetValue(), metric.Counter.GetValue(),
out,
) )
case dto.MetricType_GAUGE: case dto.MetricType_GAUGE:
if metric.Gauge == nil { if metric.Gauge == nil {
@ -86,9 +172,8 @@ func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (int, error) {
) )
} }
n, err = writeSample( n, err = writeSample(
name, metric, "", "", w, name, "", metric, "", 0,
metric.Gauge.GetValue(), metric.Gauge.GetValue(),
out,
) )
case dto.MetricType_UNTYPED: case dto.MetricType_UNTYPED:
if metric.Untyped == nil { if metric.Untyped == nil {
@ -97,9 +182,8 @@ func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (int, error) {
) )
} }
n, err = writeSample( n, err = writeSample(
name, metric, "", "", w, name, "", metric, "", 0,
metric.Untyped.GetValue(), metric.Untyped.GetValue(),
out,
) )
case dto.MetricType_SUMMARY: case dto.MetricType_SUMMARY:
if metric.Summary == nil { if metric.Summary == nil {
@ -109,29 +193,26 @@ func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (int, error) {
} }
for _, q := range metric.Summary.Quantile { for _, q := range metric.Summary.Quantile {
n, err = writeSample( n, err = writeSample(
name, metric, w, name, "", metric,
model.QuantileLabel, fmt.Sprint(q.GetQuantile()), model.QuantileLabel, q.GetQuantile(),
q.GetValue(), q.GetValue(),
out,
) )
written += n written += n
if err != nil { if err != nil {
return written, err return
} }
} }
n, err = writeSample( n, err = writeSample(
name+"_sum", metric, "", "", w, name, "_sum", metric, "", 0,
metric.Summary.GetSampleSum(), metric.Summary.GetSampleSum(),
out,
) )
if err != nil {
return written, err
}
written += n written += n
if err != nil {
return
}
n, err = writeSample( n, err = writeSample(
name+"_count", metric, "", "", w, name, "_count", metric, "", 0,
float64(metric.Summary.GetSampleCount()), float64(metric.Summary.GetSampleCount()),
out,
) )
case dto.MetricType_HISTOGRAM: case dto.MetricType_HISTOGRAM:
if metric.Histogram == nil { if metric.Histogram == nil {
@ -140,46 +221,42 @@ func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (int, error) {
) )
} }
infSeen := false infSeen := false
for _, q := range metric.Histogram.Bucket { for _, b := range metric.Histogram.Bucket {
n, err = writeSample( n, err = writeSample(
name+"_bucket", metric, w, name, "_bucket", metric,
model.BucketLabel, fmt.Sprint(q.GetUpperBound()), model.BucketLabel, b.GetUpperBound(),
float64(q.GetCumulativeCount()), float64(b.GetCumulativeCount()),
out,
) )
written += n written += n
if err != nil { if err != nil {
return written, err return
} }
if math.IsInf(q.GetUpperBound(), +1) { if math.IsInf(b.GetUpperBound(), +1) {
infSeen = true infSeen = true
} }
} }
if !infSeen { if !infSeen {
n, err = writeSample( n, err = writeSample(
name+"_bucket", metric, w, name, "_bucket", metric,
model.BucketLabel, "+Inf", model.BucketLabel, math.Inf(+1),
float64(metric.Histogram.GetSampleCount()), float64(metric.Histogram.GetSampleCount()),
out,
) )
if err != nil {
return written, err
}
written += n written += n
if err != nil {
return
}
} }
n, err = writeSample( n, err = writeSample(
name+"_sum", metric, "", "", w, name, "_sum", metric, "", 0,
metric.Histogram.GetSampleSum(), metric.Histogram.GetSampleSum(),
out,
) )
if err != nil {
return written, err
}
written += n written += n
if err != nil {
return
}
n, err = writeSample( n, err = writeSample(
name+"_count", metric, "", "", w, name, "_count", metric, "", 0,
float64(metric.Histogram.GetSampleCount()), float64(metric.Histogram.GetSampleCount()),
out,
) )
default: default:
return written, fmt.Errorf( return written, fmt.Errorf(
@ -188,116 +265,204 @@ func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (int, error) {
} }
written += n written += n
if err != nil { if err != nil {
return written, err return
} }
} }
return written, nil return
} }
// writeSample writes a single sample in text format to out, given the metric // writeSample writes a single sample in text format to w, given the metric
// name, the metric proto message itself, optionally an additional label name // name, the metric proto message itself, optionally an additional label name
// and value (use empty strings if not required), and the value. The function // with a float64 value (use empty string as label name if not required), and
// returns the number of bytes written and any error encountered. // the value. The function returns the number of bytes written and any error
// encountered.
func writeSample( func writeSample(
name string, w enhancedWriter,
name, suffix string,
metric *dto.Metric, metric *dto.Metric,
additionalLabelName, additionalLabelValue string, additionalLabelName string, additionalLabelValue float64,
value float64, value float64,
out io.Writer,
) (int, error) { ) (int, error) {
var written int var written int
n, err := fmt.Fprint(out, name) n, err := w.WriteString(name)
written += n written += n
if err != nil { if err != nil {
return written, err return written, err
} }
n, err = labelPairsToText( if suffix != "" {
metric.Label, n, err = w.WriteString(suffix)
additionalLabelName, additionalLabelValue, written += n
out, if err != nil {
return written, err
}
}
n, err = writeLabelPairs(
w, metric.Label, additionalLabelName, additionalLabelValue,
) )
written += n written += n
if err != nil { if err != nil {
return written, err return written, err
} }
n, err = fmt.Fprintf(out, " %v", value) err = w.WriteByte(' ')
written++
if err != nil {
return written, err
}
n, err = writeFloat(w, value)
written += n written += n
if err != nil { if err != nil {
return written, err return written, err
} }
if metric.TimestampMs != nil { if metric.TimestampMs != nil {
n, err = fmt.Fprintf(out, " %v", *metric.TimestampMs) err = w.WriteByte(' ')
written++
if err != nil {
return written, err
}
n, err = writeInt(w, *metric.TimestampMs)
written += n written += n
if err != nil { if err != nil {
return written, err return written, err
} }
} }
n, err = out.Write([]byte{'\n'}) err = w.WriteByte('\n')
written += n written++
if err != nil { if err != nil {
return written, err return written, err
} }
return written, nil return written, nil
} }
// labelPairsToText converts a slice of LabelPair proto messages plus the // writeLabelPairs converts a slice of LabelPair proto messages plus the
// explicitly given additional label pair into text formatted as required by the // explicitly given additional label pair into text formatted as required by the
// text format and writes it to 'out'. An empty slice in combination with an // text format and writes it to 'w'. An empty slice in combination with an empty
// empty string 'additionalLabelName' results in nothing being // string 'additionalLabelName' results in nothing being written. Otherwise, the
// written. Otherwise, the label pairs are written, escaped as required by the // label pairs are written, escaped as required by the text format, and enclosed
// text format, and enclosed in '{...}'. The function returns the number of // in '{...}'. The function returns the number of bytes written and any error
// bytes written and any error encountered. // encountered.
func labelPairsToText( func writeLabelPairs(
w enhancedWriter,
in []*dto.LabelPair, in []*dto.LabelPair,
additionalLabelName, additionalLabelValue string, additionalLabelName string, additionalLabelValue float64,
out io.Writer,
) (int, error) { ) (int, error) {
if len(in) == 0 && additionalLabelName == "" { if len(in) == 0 && additionalLabelName == "" {
return 0, nil return 0, nil
} }
var written int var (
separator := '{' written int
for _, lp := range in { separator byte = '{'
n, err := fmt.Fprintf(
out, `%c%s="%s"`,
separator, lp.GetName(), escapeString(lp.GetValue(), true),
) )
for _, lp := range in {
err := w.WriteByte(separator)
written++
if err != nil {
return written, err
}
n, err := w.WriteString(lp.GetName())
written += n written += n
if err != nil { if err != nil {
return written, err return written, err
} }
n, err = w.WriteString(`="`)
written += n
if err != nil {
return written, err
}
n, err = writeEscapedString(w, lp.GetValue(), true)
written += n
if err != nil {
return written, err
}
err = w.WriteByte('"')
written++
if err != nil {
return written, err
}
separator = ',' separator = ','
} }
if additionalLabelName != "" { if additionalLabelName != "" {
n, err := fmt.Fprintf( err := w.WriteByte(separator)
out, `%c%s="%s"`, written++
separator, additionalLabelName, if err != nil {
escapeString(additionalLabelValue, true), return written, err
) }
n, err := w.WriteString(additionalLabelName)
written += n written += n
if err != nil { if err != nil {
return written, err return written, err
} }
} n, err = w.WriteString(`="`)
n, err := out.Write([]byte{'}'})
written += n written += n
if err != nil { if err != nil {
return written, err return written, err
} }
n, err = writeFloat(w, additionalLabelValue)
written += n
if err != nil {
return written, err
}
err = w.WriteByte('"')
written++
if err != nil {
return written, err
}
}
err := w.WriteByte('}')
written++
if err != nil {
return written, err
}
return written, nil return written, nil
} }
// writeEscapedString replaces '\' by '\\', new line character by '\n', and - if
// includeDoubleQuote is true - '"' by '\"'.
var ( var (
escape = strings.NewReplacer("\\", `\\`, "\n", `\n`) escaper = strings.NewReplacer("\\", `\\`, "\n", `\n`)
escapeWithDoubleQuote = strings.NewReplacer("\\", `\\`, "\n", `\n`, "\"", `\"`) quotedEscaper = strings.NewReplacer("\\", `\\`, "\n", `\n`, "\"", `\"`)
) )
// escapeString replaces '\' by '\\', new line character by '\n', and - if func writeEscapedString(w enhancedWriter, v string, includeDoubleQuote bool) (int, error) {
// includeDoubleQuote is true - '"' by '\"'.
func escapeString(v string, includeDoubleQuote bool) string {
if includeDoubleQuote { if includeDoubleQuote {
return escapeWithDoubleQuote.Replace(v) return quotedEscaper.WriteString(w, v)
} else {
return escaper.WriteString(w, v)
}
} }
return escape.Replace(v) // writeFloat is equivalent to fmt.Fprint with a float64 argument but hardcodes
// a few common cases for increased efficiency. For non-hardcoded cases, it uses
// strconv.AppendFloat to avoid allocations, similar to writeInt.
func writeFloat(w enhancedWriter, f float64) (int, error) {
switch {
case f == 1:
return 1, w.WriteByte('1')
case f == 0:
return 1, w.WriteByte('0')
case f == -1:
return w.WriteString("-1")
case math.IsNaN(f):
return w.WriteString("NaN")
case math.IsInf(f, +1):
return w.WriteString("+Inf")
case math.IsInf(f, -1):
return w.WriteString("-Inf")
default:
bp := numBufPool.Get().(*[]byte)
*bp = strconv.AppendFloat((*bp)[:0], f, 'g', -1, 64)
written, err := w.Write(*bp)
numBufPool.Put(bp)
return written, err
}
}
// writeInt is equivalent to fmt.Fprint with an int64 argument but uses
// strconv.AppendInt with a byte slice taken from a sync.Pool to avoid
// allocations.
func writeInt(w enhancedWriter, i int64) (int, error) {
bp := numBufPool.Get().(*[]byte)
*bp = strconv.AppendInt((*bp)[:0], i, 10)
written, err := w.Write(*bp)
numBufPool.Put(bp)
return written, err
} }

View file

@ -359,7 +359,7 @@ func (p *TextParser) startLabelValue() stateFn {
} }
return p.readingValue return p.readingValue
default: default:
p.parseError(fmt.Sprintf("unexpected end of label value %q", p.currentLabelPair.Value)) p.parseError(fmt.Sprintf("unexpected end of label value %q", p.currentLabelPair.GetValue()))
return nil return nil
} }
} }

View file

@ -43,7 +43,7 @@ const (
// (1970-01-01 00:00 UTC) excluding leap seconds. // (1970-01-01 00:00 UTC) excluding leap seconds.
type Time int64 type Time int64
// Interval describes and interval between two timestamps. // Interval describes an interval between two timestamps.
type Interval struct { type Interval struct {
Start, End Time Start, End Time
} }

View file

@ -25,9 +25,21 @@ const LevelFlagName = "log.level"
// LevelFlagHelp is the help description for the log.level flag. // LevelFlagHelp is the help description for the log.level flag.
const LevelFlagHelp = "Only log messages with the given severity or above. One of: [debug, info, warn, error]" const LevelFlagHelp = "Only log messages with the given severity or above. One of: [debug, info, warn, error]"
// FormatFlagName is the canonical flag name to configure the log format
// within Prometheus projects.
const FormatFlagName = "log.format"
// FormatFlagHelp is the help description for the log.format flag.
const FormatFlagHelp = "Output format of log messages. One of: [logfmt, json]"
// AddFlags adds the flags used by this package to the Kingpin application. // AddFlags adds the flags used by this package to the Kingpin application.
// To use the default Kingpin application, call AddFlags(kingpin.CommandLine) // To use the default Kingpin application, call AddFlags(kingpin.CommandLine)
func AddFlags(a *kingpin.Application, logLevel *promlog.AllowedLevel) { func AddFlags(a *kingpin.Application, config *promlog.Config) {
config.Level = &promlog.AllowedLevel{}
a.Flag(LevelFlagName, LevelFlagHelp). a.Flag(LevelFlagName, LevelFlagHelp).
Default("info").SetValue(logLevel) Default("info").SetValue(config.Level)
config.Format = &promlog.AllowedFormat{}
a.Flag(FormatFlagName, FormatFlagHelp).
Default("logfmt").SetValue(config.Format)
} }

View file

@ -53,11 +53,43 @@ func (l *AllowedLevel) Set(s string) error {
return nil return nil
} }
// New returns a new leveled oklog logger in the logfmt format. Each logged line will be annotated // AllowedFormat is a settable identifier for the output format that the logger can have.
type AllowedFormat struct {
s string
}
func (f *AllowedFormat) String() string {
return f.s
}
// Set updates the value of the allowed format.
func (f *AllowedFormat) Set(s string) error {
switch s {
case "logfmt", "json":
f.s = s
default:
return errors.Errorf("unrecognized log format %q", s)
}
return nil
}
// Config is a struct containing configurable settings for the logger
type Config struct {
Level *AllowedLevel
Format *AllowedFormat
}
// New returns a new leveled oklog logger. Each logged line will be annotated
// with a timestamp. The output always goes to stderr. // with a timestamp. The output always goes to stderr.
func New(al AllowedLevel) log.Logger { func New(config *Config) log.Logger {
l := log.NewLogfmtLogger(log.NewSyncWriter(os.Stderr)) var l log.Logger
l = level.NewFilter(l, al.o) if config.Format.s == "logfmt" {
l = log.NewLogfmtLogger(log.NewSyncWriter(os.Stderr))
} else {
l = log.NewJSONLogger(log.NewSyncWriter(os.Stderr))
}
l = level.NewFilter(l, config.Level.o)
l = log.With(l, "ts", log.DefaultTimestampUTC, "caller", log.DefaultCaller) l = log.With(l, "ts", log.DefaultTimestampUTC, "caller", log.DefaultCaller)
return l return l
} }

View file

@ -1,10 +1,10 @@
package route package route
import ( import (
"context"
"net/http" "net/http"
"github.com/julienschmidt/httprouter" "github.com/julienschmidt/httprouter"
"golang.org/x/net/context"
) )
type param string type param string

2
vendor/modules.txt vendored
View file

@ -175,7 +175,7 @@ github.com/prometheus/client_golang/prometheus/promhttp
github.com/prometheus/client_golang/prometheus/internal github.com/prometheus/client_golang/prometheus/internal
# github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 # github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910
github.com/prometheus/client_model/go github.com/prometheus/client_model/go
# github.com/prometheus/common v0.0.0-20180518154759-7600349dcfe1 # github.com/prometheus/common v0.0.0-20181119215939-b36ad289a3ea
github.com/prometheus/common/model github.com/prometheus/common/model
github.com/prometheus/common/promlog github.com/prometheus/common/promlog
github.com/prometheus/common/promlog/flag github.com/prometheus/common/promlog/flag

View file

@ -272,7 +272,14 @@ func TestEndpoints(t *testing.T) {
al := promlog.AllowedLevel{} al := promlog.AllowedLevel{}
al.Set("debug") al.Set("debug")
remote := remote.NewStorage(promlog.New(al), func() (int64, error) { af := promlog.AllowedFormat{}
al.Set("logfmt")
promlogConfig := promlog.Config{
Level: &al,
Format: &af,
}
remote := remote.NewStorage(promlog.New(&promlogConfig), func() (int64, error) {
return 0, nil return 0, nil
}, 1*time.Second) }, 1*time.Second)