1package main
2
3import (
4 "context"
5 "errors"
6 "fmt"
7 "log/slog"
8 "os"
9 "time"
10
11 "github.com/prometheus/client_golang/prometheus"
12 "github.com/prometheus/client_golang/prometheus/promauto"
13
14 "github.com/mjl-/mox/dane"
15 "github.com/mjl-/mox/dkim"
16 "github.com/mjl-/mox/dmarc"
17 "github.com/mjl-/mox/dns"
18 "github.com/mjl-/mox/dnsbl"
19 "github.com/mjl-/mox/iprev"
20 "github.com/mjl-/mox/metrics"
21 "github.com/mjl-/mox/mlog"
22 "github.com/mjl-/mox/mtasts"
23 "github.com/mjl-/mox/smtpclient"
24 "github.com/mjl-/mox/spf"
25 "github.com/mjl-/mox/subjectpass"
26 "github.com/mjl-/mox/tlsrpt"
27 "github.com/mjl-/mox/updates"
28)
29
30var metricHTTPClient = promauto.NewHistogramVec(
31 prometheus.HistogramOpts{
32 Name: "mox_httpclient_request_duration_seconds",
33 Help: "HTTP requests lookups.",
34 Buckets: []float64{0.01, 0.05, 0.100, 0.5, 1, 5, 10, 20, 30},
35 },
36 []string{
37 "pkg",
38 "method",
39 "code",
40 "result",
41 },
42)
43
44// httpClientObserve tracks the result of an HTTP transaction in a metric, and
45// logs the result.
46func httpClientObserve(ctx context.Context, elog *slog.Logger, pkg, method string, statusCode int, err error, start time.Time) {
47 log := mlog.New("metrics", elog)
48 var result string
49 switch {
50 case err == nil:
51 switch statusCode / 100 {
52 case 2:
53 result = "ok"
54 case 4:
55 result = "usererror"
56 case 5:
57 result = "servererror"
58 default:
59 result = "other"
60 }
61 case errors.Is(err, os.ErrDeadlineExceeded) || errors.Is(err, context.DeadlineExceeded):
62 result = "timeout"
63 case errors.Is(err, context.Canceled):
64 result = "canceled"
65 default:
66 result = "error"
67 }
68 metricHTTPClient.WithLabelValues(pkg, method, result, fmt.Sprintf("%d", statusCode)).Observe(float64(time.Since(start)) / float64(time.Second))
69 log.Debugx("httpclient result", err,
70 slog.String("pkg", pkg),
71 slog.String("method", method),
72 slog.Int("code", statusCode),
73 slog.Duration("duration", time.Since(start)))
74}
75
76func init() {
77 dane.MetricVerify = promauto.NewCounter(
78 prometheus.CounterOpts{
79 Name: "mox_dane_verify_total",
80 Help: "Total number of DANE verification attempts, including mox_dane_verify_errors_total.",
81 },
82 )
83 dane.MetricVerifyErrors = promauto.NewCounter(
84 prometheus.CounterOpts{
85 Name: "mox_dane_verify_errors_total",
86 Help: "Total number of DANE verification failures, causing connections to fail.",
87 },
88 )
89
90 dkim.MetricSign = counterVec{promauto.NewCounterVec(
91 prometheus.CounterOpts{
92 Name: "mox_dkim_sign_total",
93 Help: "DKIM messages signings, label key is the type of key, rsa or ed25519.",
94 },
95 []string{
96 "key",
97 },
98 )}
99 dkim.MetricVerify = histogramVec{
100 promauto.NewHistogramVec(
101 prometheus.HistogramOpts{
102 Name: "mox_dkim_verify_duration_seconds",
103 Help: "DKIM verify, including lookup, duration and result.",
104 Buckets: []float64{0.001, 0.005, 0.01, 0.05, 0.100, 0.5, 1, 5, 10, 20},
105 },
106 []string{
107 "algorithm",
108 "status",
109 },
110 ),
111 }
112
113 dmarc.MetricVerify = histogramVec{promauto.NewHistogramVec(
114 prometheus.HistogramOpts{
115 Name: "mox_dmarc_verify_duration_seconds",
116 Help: "DMARC verify, including lookup, duration and result.",
117 Buckets: []float64{0.001, 0.005, 0.01, 0.05, 0.100, 0.5, 1, 5, 10, 20},
118 },
119 []string{
120 "status",
121 "reject", // yes/no
122 "use", // yes/no, if policy is used after random selection
123 },
124 )}
125 dns.MetricLookup = histogramVec{
126 promauto.NewHistogramVec(
127 prometheus.HistogramOpts{
128 Name: "mox_dns_lookup_duration_seconds",
129 Help: "DNS lookups.",
130 Buckets: []float64{0.001, 0.005, 0.01, 0.05, 0.100, 0.5, 1, 5, 10, 20, 30},
131 },
132 []string{
133 "pkg",
134 "type", // Lower-case Resolver method name without leading Lookup.
135 "result", // ok, nxdomain, temporary, timeout, canceled, error
136 },
137 ),
138 }
139
140 dnsbl.MetricLookup = histogramVec{promauto.NewHistogramVec(
141 prometheus.HistogramOpts{
142 Name: "mox_dnsbl_lookup_duration_seconds",
143 Help: "DNSBL lookup",
144 Buckets: []float64{0.001, 0.005, 0.01, 0.05, 0.100, 0.5, 1, 5, 10, 20},
145 },
146 []string{
147 "zone",
148 "status",
149 },
150 )}
151
152 iprev.MetricIPRev = histogramVec{promauto.NewHistogramVec(
153 prometheus.HistogramOpts{
154 Name: "mox_iprev_lookup_total",
155 Help: "Number of iprev lookups.",
156 Buckets: []float64{0.001, 0.005, 0.01, 0.05, 0.100, 0.5, 1, 5, 10, 20, 30},
157 },
158 []string{"status"},
159 )}
160
161 mtasts.MetricGet = histogramVec{promauto.NewHistogramVec(
162 prometheus.HistogramOpts{
163 Name: "mox_mtasts_get_duration_seconds",
164 Help: "MTA-STS get of policy, including lookup, duration and result.",
165 Buckets: []float64{0.01, 0.05, 0.100, 0.5, 1, 5, 10, 20},
166 },
167 []string{
168 "result", // ok, lookuperror, fetcherror
169 },
170 )}
171 mtasts.HTTPClientObserve = httpClientObserve
172
173 smtpclient.MetricCommands = histogramVec{promauto.NewHistogramVec(
174 prometheus.HistogramOpts{
175 Name: "mox_smtpclient_command_duration_seconds",
176 Help: "SMTP client command duration and result codes in seconds.",
177 Buckets: []float64{0.001, 0.005, 0.01, 0.05, 0.100, 0.5, 1, 5, 10, 20, 30, 60, 120},
178 },
179 []string{
180 "cmd",
181 "code",
182 "secode",
183 },
184 )}
185 smtpclient.MetricTLSRequiredNoIgnored = counterVec{promauto.NewCounterVec(
186 prometheus.CounterOpts{
187 Name: "mox_smtpclient_tlsrequiredno_ignored_total",
188 Help: "Connection attempts with TLS policy findings ignored due to message with TLS-Required: No header. Does not cover case where TLS certificate cannot be PKIX-verified.",
189 },
190 []string{
191 "ignored", // daneverification (no matching tlsa record)
192 },
193 )}
194 smtpclient.MetricPanicInc = func() {
195 metrics.PanicInc(metrics.Smtpclient)
196 }
197
198 spf.MetricVerify = histogramVec{promauto.NewHistogramVec(
199 prometheus.HistogramOpts{
200 Name: "mox_spf_verify_duration_seconds",
201 Help: "SPF verify, including lookup, duration and result.",
202 Buckets: []float64{0.001, 0.005, 0.01, 0.05, 0.100, 0.5, 1, 5, 10, 20},
203 },
204 []string{
205 "status",
206 },
207 )}
208
209 subjectpass.MetricGenerate = promauto.NewCounter(
210 prometheus.CounterOpts{
211 Name: "mox_subjectpass_generate_total",
212 Help: "Number of generated subjectpass challenges.",
213 },
214 )
215 subjectpass.MetricVerify = counterVec{promauto.NewCounterVec(
216 prometheus.CounterOpts{
217 Name: "mox_subjectpass_verify_total",
218 Help: "Number of subjectpass verifications.",
219 },
220 []string{
221 "result", // ok, fail
222 },
223 )}
224
225 tlsrpt.MetricLookup = histogramVec{promauto.NewHistogramVec(
226 prometheus.HistogramOpts{
227 Name: "mox_tlsrpt_lookup_duration_seconds",
228 Help: "TLSRPT lookups with result.",
229 Buckets: []float64{0.001, 0.005, 0.01, 0.05, 0.100, 0.5, 1, 5, 10, 20, 30},
230 },
231 []string{"result"},
232 )}
233
234 updates.MetricLookup = histogramVec{promauto.NewHistogramVec(
235 prometheus.HistogramOpts{
236 Name: "mox_updates_lookup_duration_seconds",
237 Help: "Updates lookup with result.",
238 Buckets: []float64{0.001, 0.005, 0.01, 0.05, 0.100, 0.5, 1, 5, 10, 20, 30},
239 },
240 []string{"result"},
241 )}
242 updates.MetricFetchChangelog = histogramVec{promauto.NewHistogramVec(
243 prometheus.HistogramOpts{
244 Name: "mox_updates_fetchchangelog_duration_seconds",
245 Help: "Fetch changelog with result.",
246 Buckets: []float64{0.001, 0.005, 0.01, 0.05, 0.100, 0.5, 1, 5, 10, 20, 30},
247 },
248 []string{"result"},
249 )}
250}
251
252type counterVec struct {
253 *prometheus.CounterVec
254}
255
256func (m counterVec) IncLabels(labels ...string) {
257 m.CounterVec.WithLabelValues(labels...).Inc()
258}
259
260type histogramVec struct {
261 *prometheus.HistogramVec
262}
263
264func (m histogramVec) ObserveLabels(v float64, labels ...string) {
265 m.HistogramVec.WithLabelValues(labels...).Observe(v)
266}
267