Skip to content

Commit a0da657

Browse files
authored
Merge pull request #94 from MrWong99/loki
feat: added loki streams processing at POST /loki/push
2 parents 66c79d4 + 32b5c0d commit a0da657

File tree

10 files changed

+1996
-318
lines changed

10 files changed

+1996
-318
lines changed

README.md

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
[![Coverage Status](https://coveralls.io/repos/github/blind-oracle/cortex-tenant/badge.svg?branch=main)](https://coveralls.io/github/blind-oracle/cortex-tenant?branch=main)
55
[![Artifact Hub](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/cortex-tenant)](https://artifacthub.io/packages/helm/cortex-tenant/cortex-tenant)
66

7-
Prometheus remote write proxy which marks timeseries with a Cortex/Mimir tenant ID based on labels.
7+
Prometheus/Loki remote write proxy which marks timeseries with a Cortex/Mimir/Loki tenant ID based on labels.
88

99
## Status
1010

@@ -16,19 +16,20 @@ The project is stable and (hopefully) almost bug-free. I will accept PRs, but pr
1616

1717
## Overview
1818

19-
Cortex/Mimir tenants (separate namespaces where metrics are stored to and queried from) are identified by `X-Scope-OrgID` HTTP header on both writes and queries.
19+
Cortex/Mimir/Loki tenants (separate namespaces where metrics are stored to and queried from) are identified by `X-Scope-OrgID` HTTP header on both writes and queries.
2020

2121
~~Problem is that Prometheus can't be configured to send this header~~ Actually in some recent version (year 2021 onwards) this functionality was added, but the tenant is the same for all jobs. This makes it impossible to use a single Prometheus (or an HA pair) to write to multiple tenants.
2222

2323
This software solves the problem using the following logic:
2424

25-
- Receive Prometheus remote write
26-
- Search each timeseries for a specific label name and extract a tenant ID from its value.
25+
- Receive Prometheus remote write or [Loki push](https://grafana.com/docs/loki/latest/reference/loki-http-api/#ingest-logs)
26+
- Search each timeseries/stream for a specific label name and extract a tenant ID from its value.
27+
For Loki the stream labels are preferred over [structured metadata](https://grafana.com/docs/loki/latest/get-started/labels/structured-metadata/) labels.
2728
If the label wasn't found then it can fall back to a configurable default ID.
28-
If none is configured then the write request will be rejected with HTTP code 400
29-
- Optionally removes this label from the timeseries
30-
- Groups timeseries by tenant
31-
- Issues a number of parallel per-tenant HTTP requests to Cortex/Mimir with the relevant tenant HTTP header (`X-Scope-OrgID` by default)
29+
If none is configured then the write/push request will be rejected with HTTP code 400
30+
- Optionally removes this label from the timeseries/stream
31+
- Groups timeseries/streams by tenant
32+
- Issues a number of parallel per-tenant HTTP requests to Cortex/Mimir/Loki with the relevant tenant HTTP header (`X-Scope-OrgID` by default)
3233

3334
## Usage
3435

@@ -38,6 +39,7 @@ This software solves the problem using the following logic:
3839

3940
- GET `/alive` returns 200 by default and 503 if the service is shutting down (if `timeout_shutdown` setting is > 0)
4041
- POST `/push` receives metrics from Prometheus - configure remote write to send here
42+
- POST `/loki/push` receives logs from Loki - configure push to send here
4143

4244
### Configuration
4345

@@ -59,6 +61,10 @@ listen_pprof: 0.0.0.0:7008
5961
# env: CT_TARGET
6062
target: http://127.0.0.1:9091/receive
6163

64+
# Where to send the modified requests (Loki)
65+
# env: CT_TARGET_LOKI
66+
target_loki: http://127.0.0.1:3100/loki/api/v1/push
67+
6268
# Whether to enable querying for IPv6 records
6369
# env: CT_ENABLE_IPV6
6470
enable_ipv6: false
@@ -137,10 +143,11 @@ tenant:
137143
- tenant
138144
- other_tenant
139145

140-
# Whether to remove the tenant label from the request
146+
# Whether to remove the tenant label from the request.
147+
# Has no effect for Loki stream messages, they are kept as is.
141148
# env: CT_TENANT_LABEL_REMOVE
142149
label_remove: true
143-
150+
144151
# To which header to add the tenant ID
145152
# env: CT_TENANT_HEADER
146153
header: X-Scope-OrgID

config.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package main
33
import (
44
"fmt"
55
"os"
6+
"slices"
67
"time"
78

89
"github.com/caarlos0/env/v8"
@@ -18,6 +19,7 @@ type config struct {
1819
MetricsIncludeTenant bool `yaml:"metrics_include_tenant" env:"CT_METRICS_INCLUDE_TENANT"`
1920

2021
Target string `env:"CT_TARGET"`
22+
TargetLoki string `yaml:"target_loki" env:"CT_TARGET_LOKI"`
2123
EnableIPv6 bool `yaml:"enable_ipv6" env:"CT_ENABLE_IPV6"`
2224

2325
LogLevel string `yaml:"log_level" env:"CT_LOG_LEVEL"`
@@ -86,6 +88,10 @@ func configLoad(file string) (*config, error) {
8688
cfg.Target = "127.0.0.1:9090"
8789
}
8890

91+
if cfg.TargetLoki == "" {
92+
cfg.TargetLoki = "127.0.0.1:3100"
93+
}
94+
8995
if cfg.Timeout == 0 {
9096
cfg.Timeout = 10 * time.Second
9197
}
@@ -109,6 +115,9 @@ func configLoad(file string) (*config, error) {
109115
// Default to the Label if list is empty
110116
if len(cfg.Tenant.LabelList) == 0 {
111117
cfg.Tenant.LabelList = append(cfg.Tenant.LabelList, cfg.Tenant.Label)
118+
} else {
119+
// Reverse entries to always prefer last label in list when found
120+
slices.Reverse(cfg.Tenant.LabelList)
112121
}
113122

114123
if cfg.Auth.Egress.Username != "" {

go.mod

Lines changed: 179 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,209 @@
11
module github.com/blind-oracle/cortex-tenant
22

3-
go 1.23
3+
go 1.25.0
44

55
require (
66
github.com/blind-oracle/go-common v1.0.7
77
github.com/caarlos0/env/v8 v8.0.0
88
github.com/gogo/protobuf v1.3.2
9-
github.com/golang/snappy v0.0.4
9+
github.com/golang/snappy v1.0.0
1010
github.com/google/uuid v1.6.0
11+
github.com/grafana/loki/v3 v3.5.4
1112
github.com/hashicorp/go-multierror v1.1.1
1213
github.com/pkg/errors v0.9.1
13-
github.com/prometheus/client_golang v1.20.5
14-
github.com/prometheus/prometheus v0.300.1
14+
github.com/prometheus/client_golang v1.21.1
15+
github.com/prometheus/prometheus v0.302.1
1516
github.com/sirupsen/logrus v1.9.3
1617
github.com/stretchr/testify v1.10.0
1718
github.com/valyala/fasthttp v1.58.0
1819
gopkg.in/yaml.v2 v2.4.0
1920
)
2021

2122
require (
23+
cloud.google.com/go/auth v0.15.0 // indirect
24+
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
25+
cloud.google.com/go/compute/metadata v0.6.0 // indirect
26+
dario.cat/mergo v1.0.1 // indirect
27+
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 // indirect
28+
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2 // indirect
29+
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
30+
github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3 // indirect
31+
github.com/DataDog/sketches-go v1.4.7 // indirect
32+
github.com/HdrHistogram/hdrhistogram-go v1.1.2 // indirect
33+
github.com/Masterminds/goutils v1.1.1 // indirect
34+
github.com/Masterminds/semver/v3 v3.3.1 // indirect
35+
github.com/Masterminds/sprig/v3 v3.3.0 // indirect
36+
github.com/Workiva/go-datastructures v1.1.5 // indirect
37+
github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b // indirect
2238
github.com/andybalholm/brotli v1.1.1 // indirect
39+
github.com/armon/go-metrics v0.4.1 // indirect
40+
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
41+
github.com/aws/aws-sdk-go v1.55.6 // indirect
42+
github.com/axiomhq/hyperloglog v0.2.5 // indirect
43+
github.com/bboreham/go-loser v0.0.0-20230920113527-fcc2c21820a3 // indirect
2344
github.com/beorn7/perks v1.0.1 // indirect
45+
github.com/c2h5oh/datasize v0.0.0-20231215233829-aa82cc1e6500 // indirect
2446
github.com/cespare/xxhash/v2 v2.3.0 // indirect
47+
github.com/coreos/go-semver v0.3.1 // indirect
48+
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
2549
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
50+
github.com/dennwc/varint v1.0.0 // indirect
51+
github.com/dgryski/go-metro v0.0.0-20180109044635-280f6062b5bc // indirect
52+
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
53+
github.com/dustin/go-humanize v1.0.1 // indirect
54+
github.com/ebitengine/purego v0.8.2 // indirect
55+
github.com/edsrzf/mmap-go v1.2.0 // indirect
56+
github.com/efficientgo/core v1.0.0-rc.3 // indirect
57+
github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb // indirect
58+
github.com/fatih/color v1.18.0 // indirect
59+
github.com/felixge/httpsnoop v1.0.4 // indirect
60+
github.com/fsnotify/fsnotify v1.8.0 // indirect
61+
github.com/go-kit/log v0.2.1 // indirect
62+
github.com/go-logfmt/logfmt v0.6.0 // indirect
63+
github.com/go-logr/logr v1.4.2 // indirect
64+
github.com/go-logr/stdr v1.2.2 // indirect
65+
github.com/go-ole/go-ole v1.3.0 // indirect
66+
github.com/go-openapi/analysis v0.23.0 // indirect
67+
github.com/go-openapi/errors v0.22.0 // indirect
68+
github.com/go-openapi/jsonpointer v0.21.0 // indirect
69+
github.com/go-openapi/jsonreference v0.21.0 // indirect
70+
github.com/go-openapi/loads v0.22.0 // indirect
71+
github.com/go-openapi/spec v0.21.0 // indirect
72+
github.com/go-openapi/strfmt v0.23.0 // indirect
73+
github.com/go-openapi/swag v0.23.0 // indirect
74+
github.com/go-openapi/validate v0.24.0 // indirect
75+
github.com/go-redsync/redsync/v4 v4.13.0 // indirect
76+
github.com/gogo/googleapis v1.4.1 // indirect
77+
github.com/gogo/status v1.1.1 // indirect
78+
github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
79+
github.com/golang/protobuf v1.5.4 // indirect
80+
github.com/google/btree v1.1.3 // indirect
81+
github.com/google/go-cmp v0.7.0 // indirect
82+
github.com/google/s2a-go v0.1.9 // indirect
83+
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
84+
github.com/googleapis/gax-go/v2 v2.14.1 // indirect
85+
github.com/gorilla/mux v1.8.1 // indirect
86+
github.com/grafana/dskit v0.0.0-20250317084829-9cdd36a91f10 // indirect
87+
github.com/grafana/gomemcache v0.0.0-20250228145437-da7b95fd2ac1 // indirect
88+
github.com/grafana/jsonparser v0.0.0-20241004153430-023329977675 // indirect
89+
github.com/grafana/loki/pkg/push v0.0.0-20240924133635-758364c7775f // indirect
90+
github.com/grafana/pyroscope-go/godeltaprof v0.1.8 // indirect
2691
github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect
92+
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.1 // indirect
93+
github.com/hashicorp/consul/api v1.31.2 // indirect
2794
github.com/hashicorp/errwrap v1.1.0 // indirect
28-
github.com/klauspost/compress v1.17.11 // indirect
29-
github.com/kr/text v0.2.0 // indirect
30-
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
95+
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
96+
github.com/hashicorp/go-hclog v1.6.3 // indirect
97+
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
98+
github.com/hashicorp/go-metrics v0.5.4 // indirect
99+
github.com/hashicorp/go-msgpack/v2 v2.1.1 // indirect
100+
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
101+
github.com/hashicorp/go-sockaddr v1.0.7 // indirect
102+
github.com/hashicorp/golang-lru v1.0.2 // indirect
103+
github.com/hashicorp/memberlist v0.5.3 // indirect
104+
github.com/hashicorp/serf v0.10.1 // indirect
105+
github.com/huandu/xstrings v1.5.0 // indirect
106+
github.com/influxdata/tdigest v0.0.2-0.20210216194612-fc98d27c9e8b // indirect
107+
github.com/jmespath/go-jmespath v0.4.0 // indirect
108+
github.com/josharian/intern v1.0.0 // indirect
109+
github.com/jpillora/backoff v1.0.0 // indirect
110+
github.com/json-iterator/go v1.1.12 // indirect
111+
github.com/julienschmidt/httprouter v1.3.0 // indirect
112+
github.com/kamstrup/intmap v0.5.1 // indirect
113+
github.com/klauspost/compress v1.18.0 // indirect
114+
github.com/kylelemons/godebug v1.1.0 // indirect
115+
github.com/lufia/plan9stats v0.0.0-20240226150601-1dcf7310316a // indirect
116+
github.com/mailru/easyjson v0.9.0 // indirect
117+
github.com/mattn/go-colorable v0.1.14 // indirect
118+
github.com/mattn/go-isatty v0.0.20 // indirect
119+
github.com/mdlayher/socket v0.5.1 // indirect
120+
github.com/mdlayher/vsock v1.2.1 // indirect
121+
github.com/miekg/dns v1.1.63 // indirect
122+
github.com/mitchellh/copystructure v1.2.0 // indirect
123+
github.com/mitchellh/go-homedir v1.1.0 // indirect
124+
github.com/mitchellh/mapstructure v1.5.1-0.20220423185008-bf980b35cac4 // indirect
125+
github.com/mitchellh/reflectwalk v1.0.2 // indirect
126+
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
127+
github.com/modern-go/reflect2 v1.0.2 // indirect
31128
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
129+
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect
130+
github.com/oklog/ulid v1.3.1 // indirect
131+
github.com/open-telemetry/opentelemetry-collector-contrib/internal/exp/metrics v0.116.0 // indirect
132+
github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatautil v0.116.0 // indirect
133+
github.com/open-telemetry/opentelemetry-collector-contrib/processor/deltatocumulativeprocessor v0.116.0 // indirect
134+
github.com/opentracing-contrib/go-grpc v0.1.1 // indirect
135+
github.com/opentracing-contrib/go-stdlib v1.1.0 // indirect
136+
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect
137+
github.com/pierrec/lz4/v4 v4.1.22 // indirect
138+
github.com/pires/go-proxyproto v0.7.0 // indirect
139+
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
32140
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
141+
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
142+
github.com/prometheus/alertmanager v0.28.1 // indirect
33143
github.com/prometheus/client_model v0.6.1 // indirect
34-
github.com/prometheus/common v0.61.0 // indirect
144+
github.com/prometheus/common v0.62.0 // indirect
145+
github.com/prometheus/exporter-toolkit v0.13.2 // indirect
35146
github.com/prometheus/procfs v0.15.1 // indirect
147+
github.com/prometheus/sigv4 v0.1.2 // indirect
148+
github.com/redis/go-redis/v9 v9.7.3 // indirect
149+
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect
150+
github.com/sercand/kuberesolver/v5 v5.1.1 // indirect
151+
github.com/shirou/gopsutil/v4 v4.25.2 // indirect
152+
github.com/shopspring/decimal v1.4.0 // indirect
153+
github.com/sony/gobreaker/v2 v2.1.0 // indirect
154+
github.com/spaolacci/murmur3 v1.1.0 // indirect
155+
github.com/spf13/cast v1.7.1 // indirect
156+
github.com/stretchr/objx v0.5.2 // indirect
157+
github.com/tjhop/slog-gokit v0.1.4 // indirect
158+
github.com/tklauser/go-sysconf v0.3.13 // indirect
159+
github.com/tklauser/numcpus v0.7.0 // indirect
160+
github.com/uber/jaeger-client-go v2.30.0+incompatible // indirect
161+
github.com/uber/jaeger-lib v2.4.1+incompatible // indirect
36162
github.com/valyala/bytebufferpool v1.0.0 // indirect
37-
golang.org/x/sys v0.28.0 // indirect
38-
golang.org/x/text v0.21.0 // indirect
39-
google.golang.org/protobuf v1.36.0 // indirect
163+
github.com/willf/bitset v1.1.11 // indirect
164+
github.com/willf/bloom v2.0.3+incompatible // indirect
165+
github.com/yusufpapurcu/wmi v1.2.4 // indirect
166+
go.etcd.io/etcd/api/v3 v3.5.4 // indirect
167+
go.etcd.io/etcd/client/pkg/v3 v3.5.4 // indirect
168+
go.etcd.io/etcd/client/v3 v3.5.4 // indirect
169+
go.mongodb.org/mongo-driver v1.17.2 // indirect
170+
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
171+
go.opentelemetry.io/collector/component v0.118.0 // indirect
172+
go.opentelemetry.io/collector/config/configtelemetry v0.118.0 // indirect
173+
go.opentelemetry.io/collector/consumer v1.24.0 // indirect
174+
go.opentelemetry.io/collector/pdata v1.28.1 // indirect
175+
go.opentelemetry.io/collector/pipeline v0.118.0 // indirect
176+
go.opentelemetry.io/collector/processor v0.118.0 // indirect
177+
go.opentelemetry.io/collector/semconv v0.118.0 // indirect
178+
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.59.0 // indirect
179+
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 // indirect
180+
go.opentelemetry.io/otel v1.35.0 // indirect
181+
go.opentelemetry.io/otel/metric v1.35.0 // indirect
182+
go.opentelemetry.io/otel/trace v1.35.0 // indirect
183+
go.uber.org/atomic v1.11.0 // indirect
184+
go.uber.org/goleak v1.3.0 // indirect
185+
go.uber.org/multierr v1.11.0 // indirect
186+
go.uber.org/zap v1.27.0 // indirect
187+
go4.org/netipx v0.0.0-20230125063823-8449b0a6169f // indirect
188+
golang.org/x/crypto v0.36.0 // indirect
189+
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect
190+
golang.org/x/mod v0.22.0 // indirect
191+
golang.org/x/net v0.38.0 // indirect
192+
golang.org/x/oauth2 v0.28.0 // indirect
193+
golang.org/x/sync v0.12.0 // indirect
194+
golang.org/x/sys v0.31.0 // indirect
195+
golang.org/x/text v0.23.0 // indirect
196+
golang.org/x/time v0.11.0 // indirect
197+
golang.org/x/tools v0.29.0 // indirect
198+
gonum.org/v1/gonum v0.8.2 // indirect
199+
google.golang.org/api v0.228.0 // indirect
200+
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect
201+
google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 // indirect
202+
google.golang.org/grpc v1.71.0 // indirect
203+
google.golang.org/protobuf v1.36.6 // indirect
40204
gopkg.in/yaml.v3 v3.0.1 // indirect
205+
k8s.io/apimachinery v0.32.3 // indirect
206+
k8s.io/client-go v0.32.1 // indirect
207+
k8s.io/klog/v2 v2.130.1 // indirect
208+
k8s.io/utils v0.0.0-20241210054802-24370beab758 // indirect
41209
)

0 commit comments

Comments
 (0)