Like Prometheus, but for logs.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
loki/pkg/tool/rules/parser.go

157 lines
3.2 KiB

package rules
import (
"bytes"
"errors"
"io"
"os"
"path/filepath"
"strings"
"github.com/prometheus/prometheus/model/rulefmt"
log "github.com/sirupsen/logrus"
yaml "gopkg.in/yaml.v3"
"github.com/grafana/loki/v3/pkg/ruler"
)
const (
LokiBackend = "loki"
)
var (
errFileReadError = errors.New("file read error")
)
// ParseFiles returns a formatted set of prometheus rule groups
func ParseFiles(files []string) (map[string]RuleNamespace, error) {
ruleSet := map[string]RuleNamespace{}
var parseFn = ParseLoki
for _, f := range files {
nss, errs := parseFn(f)
for _, err := range errs {
log.WithError(err).WithField("file", f).Errorln("unable parse rules file")
return nil, errFileReadError
}
for _, ns := range nss {
ns.Filepath = f
// Determine if the namespace is explicitly set. If not
// the file name without the extension is used.
namespace := ns.Namespace
if namespace == "" {
namespace = strings.TrimSuffix(filepath.Base(f), filepath.Ext(f))
ns.Namespace = namespace
}
_, exists := ruleSet[namespace]
if exists {
log.WithFields(log.Fields{
"namespace": namespace,
"file": f,
}).Errorln("repeated namespace attempted to be loaded")
return nil, errFileReadError
}
ruleSet[namespace] = ns
}
}
return ruleSet, nil
}
// Parse parses and validates a set of rules.
func Parse(f string) ([]RuleNamespace, []error) {
content, err := loadFile(f)
if err != nil {
log.WithError(err).WithField("file", f).Errorln("unable load rules file")
return nil, []error{errFileReadError}
}
return ParseBytes(content)
}
func ParseBytes(content []byte) ([]RuleNamespace, []error) {
decoder := yaml.NewDecoder(bytes.NewReader(content))
decoder.KnownFields(true)
var nss []RuleNamespace
for {
var ns RuleNamespace
err := decoder.Decode(&ns)
if err == io.EOF {
break
}
if err != nil {
return nil, []error{err}
}
if errs := ns.Validate(); len(errs) > 0 {
return nil, errs
}
nss = append(nss, ns)
}
return nss, nil
}
func ParseLoki(f string) ([]RuleNamespace, []error) {
content, err := loadFile(f)
if err != nil {
log.WithError(err).WithField("file", f).Errorln("unable load rules file")
return nil, []error{errFileReadError}
}
decoder := yaml.NewDecoder(bytes.NewReader(content))
decoder.KnownFields(true)
var nss []RuleNamespace
for {
var ns RuleNamespace
err := decoder.Decode(&ns)
if err == io.EOF {
break
}
if err != nil {
return nil, []error{err}
}
// the upstream loki validator only validates the rulefmt rule groups,
// not the remote write configs this type attaches.
var grps []rulefmt.RuleGroup
for _, g := range ns.Groups {
grps = append(grps, g.RuleGroup)
}
if errs := ruler.ValidateGroups(grps...); len(errs) > 0 {
return nil, errs
}
nss = append(nss, ns)
}
return nss, nil
}
func loadFile(filename string) ([]byte, error) {
file, err := os.Open(filename)
if err != nil {
return nil, err
}
defer file.Close()
fileinfo, err := file.Stat()
if err != nil {
return nil, err
}
filesize := fileinfo.Size()
buffer := make([]byte, filesize)
_, err = file.Read(buffer)
if err != nil {
return nil, err
}
return buffer, nil
}