unify loki settings

This commit is contained in:
Vadim Stepanov
2025-07-15 14:04:16 +01:00
parent f8ca04bfee
commit 81f34b4cc5
8 changed files with 162 additions and 153 deletions
@@ -61,7 +61,7 @@ func NewLokiHistorianStore(cfg setting.UnifiedAlertingStateHistorySettings, db d
if !useStore(cfg) {
return nil
}
lokiCfg, err := historian.NewLokiConfig(cfg)
lokiCfg, err := lokiclient.NewLokiConfig(cfg.LokiSettings)
if err != nil {
// this config error is already handled elsewhere
return nil
+40
View File
@@ -15,6 +15,7 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/services/ngalert/client"
"github.com/grafana/grafana/pkg/setting"
"github.com/prometheus/client_golang/prometheus"
)
@@ -45,6 +46,45 @@ type LokiConfig struct {
MaxQuerySize int
}
func NewLokiConfig(cfg setting.UnifiedAlertingLokiSettings) (LokiConfig, error) {
read, write := cfg.LokiReadURL, cfg.LokiWriteURL
if read == "" {
read = cfg.LokiRemoteURL
}
if write == "" {
write = cfg.LokiRemoteURL
}
if read == "" {
return LokiConfig{}, fmt.Errorf("either read path URL or remote Loki URL must be provided")
}
if write == "" {
return LokiConfig{}, fmt.Errorf("either write path URL or remote Loki URL must be provided")
}
readURL, err := url.Parse(read)
if err != nil {
return LokiConfig{}, fmt.Errorf("failed to parse loki remote read URL: %w", err)
}
writeURL, err := url.Parse(write)
if err != nil {
return LokiConfig{}, fmt.Errorf("failed to parse loki remote write URL: %w", err)
}
return LokiConfig{
ReadPathURL: readURL,
WritePathURL: writeURL,
BasicAuthUser: cfg.LokiBasicAuthUsername,
BasicAuthPassword: cfg.LokiBasicAuthPassword,
TenantID: cfg.LokiTenantID,
ExternalLabels: cfg.ExternalLabels,
MaxQueryLength: cfg.LokiMaxQueryLength,
MaxQuerySize: cfg.LokiMaxQuerySize,
// Snappy-compressed protobuf is the default, same goes for Promtail.
Encoder: SnappyProtoEncoder{},
}, nil
}
type HttpLokiClient struct {
client client.Requester
encoder encoder
+2 -2
View File
@@ -691,7 +691,7 @@ func configureHistorianBackend(
return historian.NewAnnotationBackend(annotationBackendLogger, store, rs, met, ac), nil
}
if backend == historian.BackendTypeLoki {
lcfg, err := historian.NewLokiConfig(cfg)
lcfg, err := lokiclient.NewLokiConfig(cfg.LokiSettings)
if err != nil {
return nil, fmt.Errorf("invalid remote loki configuration: %w", err)
}
@@ -743,7 +743,7 @@ func configureNotificationHistorian(
}
met.Info.Set(1)
lcfg, err := notifier.NewLokiConfig(cfg)
lcfg, err := lokiclient.NewLokiConfig(cfg.LokiSettings)
if err != nil {
return nil, fmt.Errorf("invalid remote loki configuration: %w", err)
}
+56 -3
View File
@@ -137,9 +137,11 @@ func TestConfigureHistorianBackend(t *testing.T) {
cfg := setting.UnifiedAlertingStateHistorySettings{
Enabled: true,
Backend: "loki",
// Should never resolve at the DNS level: https://www.rfc-editor.org/rfc/rfc6761#section-6.4
LokiReadURL: "http://gone.invalid",
LokiWriteURL: "http://gone.invalid",
LokiSettings: setting.UnifiedAlertingLokiSettings{
// Should never resolve at the DNS level: https://www.rfc-editor.org/rfc/rfc6761#section-6.4
LokiReadURL: "http://gone.invalid",
LokiWriteURL: "http://gone.invalid",
},
}
ac := &acfakes.FakeRuleService{}
@@ -232,6 +234,57 @@ grafana_alerting_state_history_info{backend="noop"} 0
})
}
func TestConfigureNotificationHistorian(t *testing.T) {
t.Run("do not fail initialization if pinging Loki fails", func(t *testing.T) {
reg := prometheus.NewRegistry()
met := metrics.NewNotificationHistorianMetrics(reg)
logger := log.NewNopLogger()
tracer := tracing.InitializeTracerForTest()
cfg := setting.UnifiedAlertingNotificationHistorySettings{
Enabled: true,
LokiSettings: setting.UnifiedAlertingLokiSettings{
// Should never resolve at the DNS level: https://www.rfc-editor.org/rfc/rfc6761#section-6.4
LokiRemoteURL: "http://gone.invalid",
},
}
h, err := configureNotificationHistorian(context.Background(), cfg, met, logger, tracer)
require.NotNil(t, h)
require.NoError(t, err)
// Verify that the metric value is set to 1, indicating that notification history is enabled.
exp := bytes.NewBufferString(`
# HELP grafana_alerting_notification_history_info Information about the notification history store.
# TYPE grafana_alerting_notification_history_info gauge
grafana_alerting_notification_history_info 1
`)
err = testutil.GatherAndCompare(reg, exp, "grafana_alerting_notification_history_info")
require.NoError(t, err)
})
t.Run("emit special zero metric if notification history disabled", func(t *testing.T) {
reg := prometheus.NewRegistry()
met := metrics.NewNotificationHistorianMetrics(reg)
logger := log.NewNopLogger()
tracer := tracing.InitializeTracerForTest()
cfg := setting.UnifiedAlertingNotificationHistorySettings{
Enabled: false,
}
h, err := configureNotificationHistorian(context.Background(), cfg, met, logger, tracer)
require.Nil(t, h)
require.NoError(t, err)
exp := bytes.NewBufferString(`
# HELP grafana_alerting_notification_history_info Information about the notification history store.
# TYPE grafana_alerting_notification_history_info gauge
grafana_alerting_notification_history_info 0
`)
err = testutil.GatherAndCompare(reg, exp, "grafana_alerting_notification_history_info")
require.NoError(t, err)
})
}
type mockDB struct {
db.DB
}
@@ -4,7 +4,6 @@ import (
"context"
"encoding/json"
"fmt"
"net/url"
"strings"
"time"
@@ -14,7 +13,6 @@ import (
"github.com/grafana/grafana/pkg/services/ngalert/client"
"github.com/grafana/grafana/pkg/services/ngalert/lokiclient"
"github.com/grafana/grafana/pkg/services/ngalert/metrics"
"github.com/grafana/grafana/pkg/setting"
"github.com/prometheus/alertmanager/notify"
"github.com/prometheus/alertmanager/types"
prometheusModel "github.com/prometheus/common/model"
@@ -105,46 +103,6 @@ func (h *NotificationHistorian) Record(ctx context.Context, alerts []*types.Aler
return errCh
}
// NewLokiConfig is a copy of historian.NewLokiConfig
func NewLokiConfig(cfg setting.UnifiedAlertingNotificationHistorySettings) (lokiclient.LokiConfig, error) {
read, write := cfg.LokiReadURL, cfg.LokiWriteURL
if read == "" {
read = cfg.LokiRemoteURL
}
if write == "" {
write = cfg.LokiRemoteURL
}
if read == "" {
return lokiclient.LokiConfig{}, fmt.Errorf("either read path URL or remote Loki URL must be provided")
}
if write == "" {
return lokiclient.LokiConfig{}, fmt.Errorf("either write path URL or remote Loki URL must be provided")
}
readURL, err := url.Parse(read)
if err != nil {
return lokiclient.LokiConfig{}, fmt.Errorf("failed to parse loki remote read URL: %w", err)
}
writeURL, err := url.Parse(write)
if err != nil {
return lokiclient.LokiConfig{}, fmt.Errorf("failed to parse loki remote write URL: %w", err)
}
return lokiclient.LokiConfig{
ReadPathURL: readURL,
WritePathURL: writeURL,
BasicAuthUser: cfg.LokiBasicAuthUsername,
BasicAuthPassword: cfg.LokiBasicAuthPassword,
TenantID: cfg.LokiTenantID,
ExternalLabels: cfg.ExternalLabels,
MaxQueryLength: cfg.LokiMaxQueryLength,
MaxQuerySize: cfg.LokiMaxQuerySize,
// Snappy-compressed protobuf is the default, same goes for Promtail.
Encoder: lokiclient.SnappyProtoEncoder{},
}, nil
}
func (h *NotificationHistorian) prepareStream(ctx context.Context, alerts []*types.Alert, notificationError error) (lokiclient.Stream, error) {
receiverName, ok := notify.ReceiverName(ctx)
if !ok {
@@ -5,7 +5,6 @@ import (
"encoding/json"
"fmt"
"math"
"net/url"
"regexp"
"sort"
"strconv"
@@ -15,7 +14,6 @@ import (
"github.com/benbjohnson/clock"
"github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/grafana/grafana/pkg/services/ngalert/lokiclient"
"github.com/grafana/grafana/pkg/setting"
"go.opentelemetry.io/otel/trace"
"github.com/grafana/grafana/pkg/apimachinery/errutil"
@@ -541,42 +539,3 @@ func (h *RemoteLokiBackend) getFolderUIDsForFilter(ctx context.Context, query mo
sort.Strings(uids)
return uids, nil
}
func NewLokiConfig(cfg setting.UnifiedAlertingStateHistorySettings) (lokiclient.LokiConfig, error) {
read, write := cfg.LokiReadURL, cfg.LokiWriteURL
if read == "" {
read = cfg.LokiRemoteURL
}
if write == "" {
write = cfg.LokiRemoteURL
}
if read == "" {
return lokiclient.LokiConfig{}, fmt.Errorf("either read path URL or remote Loki URL must be provided")
}
if write == "" {
return lokiclient.LokiConfig{}, fmt.Errorf("either write path URL or remote Loki URL must be provided")
}
readURL, err := url.Parse(read)
if err != nil {
return lokiclient.LokiConfig{}, fmt.Errorf("failed to parse loki remote read URL: %w", err)
}
writeURL, err := url.Parse(write)
if err != nil {
return lokiclient.LokiConfig{}, fmt.Errorf("failed to parse loki remote write URL: %w", err)
}
return lokiclient.LokiConfig{
ReadPathURL: readURL,
WritePathURL: writeURL,
BasicAuthUser: cfg.LokiBasicAuthUsername,
BasicAuthPassword: cfg.LokiBasicAuthPassword,
TenantID: cfg.LokiTenantID,
ExternalLabels: cfg.ExternalLabels,
MaxQueryLength: cfg.LokiMaxQueryLength,
MaxQuerySize: cfg.LokiMaxQuerySize,
// Snappy-compressed protobuf is the default, same goes for Promtail.
Encoder: lokiclient.SnappyProtoEncoder{},
}, nil
}
@@ -42,7 +42,7 @@ func TestLokiConfig(t *testing.T) {
t.Run("test URL options", func(t *testing.T) {
type testCase struct {
name string
in setting.UnifiedAlertingStateHistorySettings
in setting.UnifiedAlertingLokiSettings
expRead string
expWrite string
expErr string
@@ -51,7 +51,7 @@ func TestLokiConfig(t *testing.T) {
cases := []testCase{
{
name: "remote url only",
in: setting.UnifiedAlertingStateHistorySettings{
in: setting.UnifiedAlertingLokiSettings{
LokiRemoteURL: "http://url.com",
},
expRead: "http://url.com",
@@ -59,7 +59,7 @@ func TestLokiConfig(t *testing.T) {
},
{
name: "separate urls",
in: setting.UnifiedAlertingStateHistorySettings{
in: setting.UnifiedAlertingLokiSettings{
LokiReadURL: "http://read.url.com",
LokiWriteURL: "http://write.url.com",
},
@@ -68,7 +68,7 @@ func TestLokiConfig(t *testing.T) {
},
{
name: "single fallback",
in: setting.UnifiedAlertingStateHistorySettings{
in: setting.UnifiedAlertingLokiSettings{
LokiRemoteURL: "http://url.com",
LokiReadURL: "http://read.url.com",
},
@@ -77,21 +77,21 @@ func TestLokiConfig(t *testing.T) {
},
{
name: "missing read",
in: setting.UnifiedAlertingStateHistorySettings{
in: setting.UnifiedAlertingLokiSettings{
LokiWriteURL: "http://url.com",
},
expErr: "either read path URL or remote",
},
{
name: "missing write",
in: setting.UnifiedAlertingStateHistorySettings{
in: setting.UnifiedAlertingLokiSettings{
LokiReadURL: "http://url.com",
},
expErr: "either write path URL or remote",
},
{
name: "invalid",
in: setting.UnifiedAlertingStateHistorySettings{
in: setting.UnifiedAlertingLokiSettings{
LokiRemoteURL: "://://",
},
expErr: "failed to parse",
@@ -100,7 +100,7 @@ func TestLokiConfig(t *testing.T) {
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
res, err := NewLokiConfig(tc.in)
res, err := lokiclient.NewLokiConfig(tc.in)
if tc.expErr != "" {
require.ErrorContains(t, err, tc.expErr)
} else {
@@ -112,12 +112,12 @@ func TestLokiConfig(t *testing.T) {
})
t.Run("captures external labels", func(t *testing.T) {
set := setting.UnifiedAlertingStateHistorySettings{
set := setting.UnifiedAlertingLokiSettings{
LokiRemoteURL: "http://url.com",
ExternalLabels: map[string]string{"a": "b"},
}
res, err := NewLokiConfig(set)
res, err := lokiclient.NewLokiConfig(set)
require.NoError(t, err)
require.Contains(t, res.ExternalLabels, "a")
@@ -454,7 +454,7 @@ func TestMerge(t *testing.T) {
"extra": "label",
},
Values: []lokiclient.Sample{
{time.Unix(1, 0), `{"schemaVersion": 1, "previous": "normal", "current": "pending", "values":{"a": 1.5}, "ruleUID": "test-rule-1"}`},
{T: time.Unix(1, 0), V: `{"schemaVersion": 1, "previous": "normal", "current": "pending", "values":{"a": 1.5}, "ruleUID": "test-rule-1"}`},
},
},
{
@@ -465,7 +465,7 @@ func TestMerge(t *testing.T) {
"folderUID": "test-folder-1",
},
Values: []lokiclient.Sample{
{time.Unix(2, 0), `{"schemaVersion": 1, "previous": "pending", "current": "firing", "values":{"a": 2.5}, "ruleUID": "test-rule-2"}`},
{T: time.Unix(2, 0), V: `{"schemaVersion": 1, "previous": "pending", "current": "firing", "values":{"a": 2.5}, "ruleUID": "test-rule-2"}`},
},
},
},
@@ -530,8 +530,8 @@ func TestMerge(t *testing.T) {
"folderUID": "test-folder-1",
},
Values: []lokiclient.Sample{
{time.Unix(1, 0), `{"schemaVersion": 1, "previous": "normal", "current": "pending", "values":{"a": 1.5}, "ruleUID": "test-rule-1"}`},
{time.Unix(5, 0), `{"schemaVersion": 1, "previous": "pending", "current": "normal", "values":{"a": 0.5}, "ruleUID": "test-rule-2"}`},
{T: time.Unix(1, 0), V: `{"schemaVersion": 1, "previous": "normal", "current": "pending", "values":{"a": 1.5}, "ruleUID": "test-rule-1"}`},
{T: time.Unix(5, 0), V: `{"schemaVersion": 1, "previous": "pending", "current": "normal", "values":{"a": 0.5}, "ruleUID": "test-rule-2"}`},
},
},
{
@@ -542,7 +542,7 @@ func TestMerge(t *testing.T) {
"folderUID": "test-folder-1",
},
Values: []lokiclient.Sample{
{time.Unix(2, 0), `{"schemaVersion": 1, "previous": "pending", "current": "firing", "values":{"a": 2.5}, "ruleUID": "test-rule-3"}`},
{T: time.Unix(2, 0), V: `{"schemaVersion": 1, "previous": "pending", "current": "firing", "values":{"a": 2.5}, "ruleUID": "test-rule-3"}`},
},
},
},
@@ -595,8 +595,8 @@ func TestMerge(t *testing.T) {
"folderUID": "test-folder-1",
},
Values: []lokiclient.Sample{
{time.Unix(1, 0), `{"schemaVersion": 1, "previous": "normal", "current": "pending", "values":{"a": 1.5}, "ruleUID": "test-rule-1"}`},
{time.Unix(5, 0), `{"schemaVersion": 1, "previous": "pending", "current": "normal", "values":{"a": 0.5}, "ruleUID": "test-rule-2"}`},
{T: time.Unix(1, 0), V: `{"schemaVersion": 1, "previous": "normal", "current": "pending", "values":{"a": 1.5}, "ruleUID": "test-rule-1"}`},
{T: time.Unix(5, 0), V: `{"schemaVersion": 1, "previous": "pending", "current": "normal", "values":{"a": 0.5}, "ruleUID": "test-rule-2"}`},
},
},
{
@@ -607,7 +607,7 @@ func TestMerge(t *testing.T) {
"folderUID": "test-folder-2",
},
Values: []lokiclient.Sample{
{time.Unix(2, 0), `{"schemaVersion": 1, "previous": "pending", "current": "firing", "values":{"a": 2.5}, "ruleUID": "test-rule-3"}`},
{T: time.Unix(2, 0), V: `{"schemaVersion": 1, "previous": "pending", "current": "firing", "values":{"a": 2.5}, "ruleUID": "test-rule-3"}`},
},
},
},
@@ -649,8 +649,8 @@ func TestMerge(t *testing.T) {
"group": "test-group-1",
},
Values: []lokiclient.Sample{
{time.Unix(1, 0), `{"schemaVersion": 1, "previous": "normal", "current": "pending", "values":{"a": 1.5}, "ruleUID": "test-rule-1"}`},
{time.Unix(5, 0), `{"schemaVersion": 1, "previous": "pending", "current": "normal", "values":{"a": 0.5}, "ruleUID": "test-rule-2"}`},
{T: time.Unix(1, 0), V: `{"schemaVersion": 1, "previous": "normal", "current": "pending", "values":{"a": 1.5}, "ruleUID": "test-rule-1"}`},
{T: time.Unix(5, 0), V: `{"schemaVersion": 1, "previous": "pending", "current": "normal", "values":{"a": 0.5}, "ruleUID": "test-rule-2"}`},
},
},
},
@@ -673,7 +673,7 @@ func TestMerge(t *testing.T) {
"group": "test-group-1",
},
Values: []lokiclient.Sample{
{time.Unix(1, 0), `{"schemaVersion": 1, "previous": "normal", "current": "pending", "values":{"a": 1.5}, "ruleUID": "test-rule-1"}`},
{T: time.Unix(1, 0), V: `{"schemaVersion": 1, "previous": "normal", "current": "pending", "values":{"a": 1.5}, "ruleUID": "test-rule-1"}`},
},
},
},
+42 -43
View File
@@ -183,29 +183,7 @@ type UnifiedAlertingPrometheusConversionSettings struct {
RuleQueryOffset time.Duration
}
type UnifiedAlertingStateHistorySettings struct {
Enabled bool
Backend string
LokiRemoteURL string
LokiReadURL string
LokiWriteURL string
LokiTenantID string
// LokiBasicAuthUsername and LokiBasicAuthPassword are used for basic auth
// if one of them is set.
LokiBasicAuthPassword string
LokiBasicAuthUsername string
LokiMaxQueryLength time.Duration
LokiMaxQuerySize int
PrometheusMetricName string
PrometheusTargetDatasourceUID string
PrometheusWriteTimeout time.Duration
MultiPrimary string
MultiSecondaries []string
ExternalLabels map[string]string
}
type UnifiedAlertingNotificationHistorySettings struct {
Enabled bool
type UnifiedAlertingLokiSettings struct {
LokiRemoteURL string
LokiReadURL string
LokiWriteURL string
@@ -219,6 +197,23 @@ type UnifiedAlertingNotificationHistorySettings struct {
ExternalLabels map[string]string
}
type UnifiedAlertingStateHistorySettings struct {
Enabled bool
Backend string
LokiSettings UnifiedAlertingLokiSettings
PrometheusMetricName string
PrometheusTargetDatasourceUID string
PrometheusWriteTimeout time.Duration
MultiPrimary string
MultiSecondaries []string
ExternalLabels map[string]string
}
type UnifiedAlertingNotificationHistorySettings struct {
Enabled bool
LokiSettings UnifiedAlertingLokiSettings
}
// IsEnabled returns true if UnifiedAlertingSettings.Enabled is either nil or true.
// It hides the implementation details of the Enabled and simplifies its usage.
func (u *UnifiedAlertingSettings) IsEnabled() bool {
@@ -470,16 +465,18 @@ func (cfg *Cfg) ReadUnifiedAlertingSettings(iniFile *ini.File) error {
stateHistory := iniFile.Section("unified_alerting.state_history")
stateHistoryLabels := iniFile.Section("unified_alerting.state_history.external_labels")
uaCfgStateHistory := UnifiedAlertingStateHistorySettings{
Enabled: stateHistory.Key("enabled").MustBool(stateHistoryDefaultEnabled),
Backend: stateHistory.Key("backend").MustString("annotations"),
LokiRemoteURL: stateHistory.Key("loki_remote_url").MustString(""),
LokiReadURL: stateHistory.Key("loki_remote_read_url").MustString(""),
LokiWriteURL: stateHistory.Key("loki_remote_write_url").MustString(""),
LokiTenantID: stateHistory.Key("loki_tenant_id").MustString(""),
LokiBasicAuthUsername: stateHistory.Key("loki_basic_auth_username").MustString(""),
LokiBasicAuthPassword: stateHistory.Key("loki_basic_auth_password").MustString(""),
LokiMaxQueryLength: stateHistory.Key("loki_max_query_length").MustDuration(lokiDefaultMaxQueryLength),
LokiMaxQuerySize: stateHistory.Key("loki_max_query_size").MustInt(lokiDefaultMaxQuerySize),
Enabled: stateHistory.Key("enabled").MustBool(stateHistoryDefaultEnabled),
Backend: stateHistory.Key("backend").MustString("annotations"),
LokiSettings: UnifiedAlertingLokiSettings{
LokiRemoteURL: stateHistory.Key("loki_remote_url").MustString(""),
LokiReadURL: stateHistory.Key("loki_remote_read_url").MustString(""),
LokiWriteURL: stateHistory.Key("loki_remote_write_url").MustString(""),
LokiTenantID: stateHistory.Key("loki_tenant_id").MustString(""),
LokiBasicAuthUsername: stateHistory.Key("loki_basic_auth_username").MustString(""),
LokiBasicAuthPassword: stateHistory.Key("loki_basic_auth_password").MustString(""),
LokiMaxQueryLength: stateHistory.Key("loki_max_query_length").MustDuration(lokiDefaultMaxQueryLength),
LokiMaxQuerySize: stateHistory.Key("loki_max_query_size").MustInt(lokiDefaultMaxQuerySize),
},
MultiPrimary: stateHistory.Key("primary").MustString(""),
MultiSecondaries: splitTrim(stateHistory.Key("secondaries").MustString(""), ","),
PrometheusMetricName: stateHistory.Key("prometheus_metric_name").MustString(defaultHistorianPrometheusMetricName),
@@ -492,16 +489,18 @@ func (cfg *Cfg) ReadUnifiedAlertingSettings(iniFile *ini.File) error {
notificationHistory := iniFile.Section("unified_alerting.notification_history")
notificationHistoryLabels := iniFile.Section("unified_alerting.notification_history.external_labels")
uaCfgNotificationHistory := UnifiedAlertingNotificationHistorySettings{
Enabled: notificationHistory.Key("enabled").MustBool(notificationHistoryDefaultEnabled),
LokiRemoteURL: notificationHistory.Key("loki_remote_url").MustString(""),
LokiReadURL: notificationHistory.Key("loki_remote_read_url").MustString(""),
LokiWriteURL: notificationHistory.Key("loki_remote_write_url").MustString(""),
LokiTenantID: notificationHistory.Key("loki_tenant_id").MustString(""),
LokiBasicAuthUsername: notificationHistory.Key("loki_basic_auth_username").MustString(""),
LokiBasicAuthPassword: notificationHistory.Key("loki_basic_auth_password").MustString(""),
LokiMaxQueryLength: notificationHistory.Key("loki_max_query_length").MustDuration(lokiDefaultMaxQueryLength),
LokiMaxQuerySize: notificationHistory.Key("loki_max_query_size").MustInt(lokiDefaultMaxQuerySize),
ExternalLabels: notificationHistoryLabels.KeysHash(),
Enabled: notificationHistory.Key("enabled").MustBool(notificationHistoryDefaultEnabled),
LokiSettings: UnifiedAlertingLokiSettings{
LokiRemoteURL: notificationHistory.Key("loki_remote_url").MustString(""),
LokiReadURL: notificationHistory.Key("loki_remote_read_url").MustString(""),
LokiWriteURL: notificationHistory.Key("loki_remote_write_url").MustString(""),
LokiTenantID: notificationHistory.Key("loki_tenant_id").MustString(""),
LokiBasicAuthUsername: notificationHistory.Key("loki_basic_auth_username").MustString(""),
LokiBasicAuthPassword: notificationHistory.Key("loki_basic_auth_password").MustString(""),
LokiMaxQueryLength: notificationHistory.Key("loki_max_query_length").MustDuration(lokiDefaultMaxQueryLength),
LokiMaxQuerySize: notificationHistory.Key("loki_max_query_size").MustInt(lokiDefaultMaxQuerySize),
ExternalLabels: notificationHistoryLabels.KeysHash(),
},
}
uaCfg.NotificationHistory = uaCfgNotificationHistory