github.com/yankunsam/loki/v2@v2.6.3-0.20220817130409-389df5235c27/clients/cmd/docker-driver/config.go (about) 1 package main 2 3 import ( 4 "bytes" 5 "fmt" 6 "io/ioutil" 7 "net/url" 8 "os" 9 "strconv" 10 "strings" 11 "time" 12 13 "github.com/docker/docker/daemon/logger" 14 "github.com/docker/docker/daemon/logger/templates" 15 "github.com/grafana/dskit/backoff" 16 "github.com/grafana/dskit/flagext" 17 "github.com/pkg/errors" 18 "github.com/prometheus/common/model" 19 "github.com/prometheus/prometheus/model/labels" 20 "github.com/prometheus/prometheus/model/relabel" 21 "gopkg.in/yaml.v2" 22 23 "github.com/grafana/loki/clients/pkg/logentry/stages" 24 "github.com/grafana/loki/clients/pkg/promtail/client" 25 "github.com/grafana/loki/clients/pkg/promtail/targets/file" 26 27 "github.com/grafana/loki/pkg/util" 28 ) 29 30 const ( 31 driverName = "loki" 32 33 cfgExternalLabelsKey = "loki-external-labels" 34 cfgURLKey = "loki-url" 35 cfgTLSCAFileKey = "loki-tls-ca-file" 36 cfgTLSCertFileKey = "loki-tls-cert-file" 37 cfgTLSKeyFileKey = "loki-tls-key-file" 38 cfgTLSServerNameKey = "loki-tls-server-name" 39 cfgTLSInsecure = "loki-tls-insecure-skip-verify" 40 cfgProxyURLKey = "loki-proxy-url" 41 cfgTimeoutKey = "loki-timeout" 42 cfgBatchWaitKey = "loki-batch-wait" 43 cfgBatchSizeKey = "loki-batch-size" 44 cfgMinBackoffKey = "loki-min-backoff" 45 cfgMaxBackoffKey = "loki-max-backoff" 46 cfgMaxRetriesKey = "loki-retries" 47 cfgPipelineStagesFileKey = "loki-pipeline-stage-file" 48 cfgPipelineStagesKey = "loki-pipeline-stages" 49 cfgTenantIDKey = "loki-tenant-id" 50 cfgNofile = "no-file" 51 cfgKeepFile = "keep-file" 52 cfgRelabelKey = "loki-relabel-config" 53 54 swarmServiceLabelKey = "com.docker.swarm.service.name" 55 swarmStackLabelKey = "com.docker.stack.namespace" 56 57 swarmServiceLabelName = "swarm_service" 58 swarmStackLabelName = "swarm_stack" 59 60 composeServiceLabelKey = "com.docker.compose.service" 61 composeProjectLabelKey = "com.docker.compose.project" 62 63 composeServiceLabelName = "compose_service" 64 composeProjectLabelName = "compose_project" 65 66 defaultExternalLabels = "container_name={{.Name}}" 67 defaultHostLabelName = model.LabelName("host") 68 ) 69 70 var ( 71 defaultClientConfig = client.Config{ 72 BatchWait: client.BatchWait, 73 BatchSize: client.BatchSize, 74 BackoffConfig: backoff.Config{ 75 MinBackoff: client.MinBackoff, 76 MaxBackoff: client.MaxBackoff, 77 MaxRetries: client.MaxRetries, 78 }, 79 Timeout: client.Timeout, 80 } 81 ) 82 83 type config struct { 84 labels model.LabelSet 85 clientConfig client.Config 86 pipeline PipelineConfig 87 } 88 89 type PipelineConfig struct { 90 PipelineStages stages.PipelineStages `yaml:"pipeline_stages,omitempty"` 91 } 92 93 func validateDriverOpt(loggerInfo logger.Info) error { 94 config := loggerInfo.Config 95 96 for opt := range config { 97 switch opt { 98 case cfgURLKey: 99 case cfgExternalLabelsKey: 100 case cfgTLSCAFileKey: 101 case cfgTLSCertFileKey: 102 case cfgTLSKeyFileKey: 103 case cfgTLSServerNameKey: 104 case cfgTLSInsecure: 105 case cfgTimeoutKey: 106 case cfgProxyURLKey: 107 case cfgBatchWaitKey: 108 case cfgBatchSizeKey: 109 case cfgMinBackoffKey: 110 case cfgMaxBackoffKey: 111 case cfgMaxRetriesKey: 112 case cfgPipelineStagesKey: 113 case cfgPipelineStagesFileKey: 114 case cfgTenantIDKey: 115 case cfgRelabelKey: 116 case cfgNofile: 117 case cfgKeepFile: 118 case "labels": 119 case "env": 120 case "env-regex": 121 case "max-size": 122 case "max-file": 123 case "mode": 124 case "max-buffer-size": 125 default: 126 return fmt.Errorf("%s: wrong log-opt: '%s' - %s", driverName, opt, loggerInfo.ContainerID) 127 } 128 } 129 _, ok := config[cfgURLKey] 130 if !ok { 131 return fmt.Errorf("%s: %s is required in the config", driverName, cfgURLKey) 132 } 133 134 return nil 135 } 136 137 func parseConfig(logCtx logger.Info) (*config, error) { 138 if err := validateDriverOpt(logCtx); err != nil { 139 return nil, err 140 } 141 142 clientConfig := defaultClientConfig 143 labels := model.LabelSet{} 144 145 // parse URL 146 rawURL, ok := logCtx.Config[cfgURLKey] 147 if !ok { 148 return nil, fmt.Errorf("%s: option %s is required", driverName, cfgURLKey) 149 } 150 url, err := url.Parse(rawURL) 151 if err != nil { 152 return nil, fmt.Errorf("%s: option %s is invalid %s", driverName, cfgURLKey, err) 153 } 154 clientConfig.URL = flagext.URLValue{URL: url} 155 156 // parse timeout 157 if err := parseDuration(cfgTimeoutKey, logCtx, func(d time.Duration) { clientConfig.Timeout = d }); err != nil { 158 return nil, err 159 } 160 161 // parse batch wait and batch size 162 if err := parseDuration(cfgBatchWaitKey, logCtx, func(d time.Duration) { clientConfig.BatchWait = d }); err != nil { 163 return nil, err 164 } 165 if err := parseInt(cfgBatchSizeKey, logCtx, func(i int) { clientConfig.BatchSize = i }); err != nil { 166 return nil, err 167 } 168 169 // parse backoff 170 if err := parseDuration(cfgMinBackoffKey, logCtx, func(d time.Duration) { clientConfig.BackoffConfig.MinBackoff = d }); err != nil { 171 return nil, err 172 } 173 if err := parseDuration(cfgMaxBackoffKey, logCtx, func(d time.Duration) { clientConfig.BackoffConfig.MaxBackoff = d }); err != nil { 174 return nil, err 175 } 176 if err := parseInt(cfgMaxRetriesKey, logCtx, func(i int) { clientConfig.BackoffConfig.MaxRetries = i }); err != nil { 177 return nil, err 178 } 179 180 // parse http & tls config 181 if tlsCAFile, ok := logCtx.Config[cfgTLSCAFileKey]; ok { 182 clientConfig.Client.TLSConfig.CAFile = tlsCAFile 183 } 184 if tlsCertFile, ok := logCtx.Config[cfgTLSCertFileKey]; ok { 185 clientConfig.Client.TLSConfig.CertFile = tlsCertFile 186 } 187 if tlsKeyFile, ok := logCtx.Config[cfgTLSKeyFileKey]; ok { 188 clientConfig.Client.TLSConfig.KeyFile = tlsKeyFile 189 } 190 if tlsServerName, ok := logCtx.Config[cfgTLSServerNameKey]; ok { 191 clientConfig.Client.TLSConfig.ServerName = tlsServerName 192 } 193 if tlsInsecureSkipRaw, ok := logCtx.Config[cfgTLSInsecure]; ok { 194 tlsInsecureSkip, err := strconv.ParseBool(tlsInsecureSkipRaw) 195 if err != nil { 196 return nil, fmt.Errorf("%s: invalid external labels: %s", driverName, tlsInsecureSkipRaw) 197 } 198 clientConfig.Client.TLSConfig.InsecureSkipVerify = tlsInsecureSkip 199 } 200 if tlsProxyURL, ok := logCtx.Config[cfgProxyURLKey]; ok { 201 proxyURL, err := url.Parse(tlsProxyURL) 202 if err != nil { 203 return nil, fmt.Errorf("%s: option %s is invalid %s", driverName, cfgProxyURLKey, err) 204 } 205 clientConfig.Client.ProxyURL.URL = proxyURL 206 } 207 208 // parse tenant id 209 tenantID, ok := logCtx.Config[cfgTenantIDKey] 210 if ok && tenantID != "" { 211 clientConfig.TenantID = tenantID 212 } 213 214 // parse external labels 215 extlbs, ok := logCtx.Config[cfgExternalLabelsKey] 216 if !ok { 217 extlbs = defaultExternalLabels 218 } 219 lvs := strings.Split(extlbs, ",") 220 for _, lv := range lvs { 221 lvparts := strings.Split(lv, "=") 222 if len(lvparts) != 2 { 223 return nil, fmt.Errorf("%s: invalid external labels: %s", driverName, extlbs) 224 } 225 labelName := model.LabelName(lvparts[0]) 226 if !labelName.IsValid() { 227 return nil, fmt.Errorf("%s: invalid external label name: %s", driverName, labelName) 228 } 229 230 // expand the value using docker template {{.Name}}.{{.ImageName}} 231 value, err := expandLabelValue(logCtx, lvparts[1]) 232 if err != nil { 233 return nil, fmt.Errorf("%s: could not expand label value: %s err : %s", driverName, lvparts[1], err) 234 } 235 labelValue := model.LabelValue(value) 236 if !labelValue.IsValid() { 237 return nil, fmt.Errorf("%s: invalid external label value: %s", driverName, value) 238 } 239 labels[labelName] = labelValue 240 } 241 242 // other labels coming from docker labels or env selected by user labels, labels-regex, env, env-regex config. 243 attrs, err := logCtx.ExtraAttributes(func(label string) string { 244 return strings.ReplaceAll(strings.ReplaceAll(label, "-", "_"), ".", "_") 245 }) 246 if err != nil { 247 return nil, err 248 } 249 250 // parse docker swarms labels and adds them automatically to attrs 251 swarmService := logCtx.ContainerLabels[swarmServiceLabelKey] 252 if swarmService != "" { 253 attrs[swarmServiceLabelName] = swarmService 254 } 255 swarmStack := logCtx.ContainerLabels[swarmStackLabelKey] 256 if swarmStack != "" { 257 attrs[swarmStackLabelName] = swarmStack 258 } 259 260 // parse docker compose labels and adds them automatically to attrs 261 composeService := logCtx.ContainerLabels[composeServiceLabelKey] 262 if composeService != "" { 263 attrs[composeServiceLabelName] = composeService 264 } 265 composeProject := logCtx.ContainerLabels[composeProjectLabelKey] 266 if composeProject != "" { 267 attrs[composeProjectLabelName] = composeProject 268 } 269 270 for key, value := range attrs { 271 labelName := model.LabelName(key) 272 if !labelName.IsValid() { 273 return nil, fmt.Errorf("%s: invalid label name from attribute: %s", driverName, key) 274 } 275 labelValue := model.LabelValue(value) 276 if !labelValue.IsValid() { 277 return nil, fmt.Errorf("%s: invalid label value from attribute: %s", driverName, value) 278 } 279 labels[labelName] = labelValue 280 } 281 282 // adds host label and filename 283 host, err := os.Hostname() 284 if err == nil { 285 labels[defaultHostLabelName] = model.LabelValue(host) 286 } 287 labels[file.FilenameLabel] = model.LabelValue(logCtx.LogPath) 288 289 // Process relabel configs. 290 if relabelString, ok := logCtx.Config[cfgRelabelKey]; ok && relabelString != "" { 291 relabeled, err := relabelConfig(relabelString, labels) 292 if err != nil { 293 return nil, fmt.Errorf("error applying relabel config: %s err: %s", relabelString, err) 294 } 295 labels = relabeled 296 } 297 298 // parse pipeline stages 299 pipeline, err := parsePipeline(logCtx) 300 if err != nil { 301 return nil, err 302 } 303 return &config{ 304 labels: labels, 305 clientConfig: clientConfig, 306 pipeline: pipeline, 307 }, nil 308 } 309 310 func parsePipeline(logCtx logger.Info) (PipelineConfig, error) { 311 var pipeline PipelineConfig 312 pipelineFile, okFile := logCtx.Config[cfgPipelineStagesFileKey] 313 pipelineString, okString := logCtx.Config[cfgPipelineStagesKey] 314 if okFile && okString { 315 return pipeline, fmt.Errorf("only one of %s or %s can be configured", cfgPipelineStagesFileKey, cfgPipelineStagesFileKey) 316 } 317 if okFile { 318 if err := loadConfig(pipelineFile, &pipeline); err != nil { 319 return pipeline, fmt.Errorf("error loading config file %s: %s", pipelineFile, err) 320 } 321 } 322 if okString { 323 if err := yaml.UnmarshalStrict([]byte(pipelineString), &pipeline.PipelineStages); err != nil { 324 return pipeline, err 325 } 326 } 327 return pipeline, nil 328 } 329 330 func expandLabelValue(info logger.Info, defaultTemplate string) (string, error) { 331 tmpl, err := templates.NewParse("label_value", defaultTemplate) 332 if err != nil { 333 return "", err 334 } 335 buf := new(bytes.Buffer) 336 if err := tmpl.Execute(buf, &info); err != nil { 337 return "", err 338 } 339 340 return buf.String(), nil 341 } 342 343 func parseDuration(key string, logCtx logger.Info, set func(d time.Duration)) error { 344 if raw, ok := logCtx.Config[key]; ok { 345 val, err := time.ParseDuration(raw) 346 if err != nil { 347 return fmt.Errorf("%s: invalid option %s format: %s", driverName, key, raw) 348 } 349 set(val) 350 } 351 return nil 352 } 353 354 func parseInt(key string, logCtx logger.Info, set func(i int)) error { 355 if raw, ok := logCtx.Config[key]; ok { 356 val, err := strconv.Atoi(raw) 357 if err != nil { 358 return fmt.Errorf("%s: invalid option %s format: %s", driverName, key, raw) 359 } 360 set(val) 361 } 362 return nil 363 } 364 365 func relabelConfig(config string, lbs model.LabelSet) (model.LabelSet, error) { 366 relabelConfig := make([]*relabel.Config, 0) 367 if err := yaml.UnmarshalStrict([]byte(config), &relabelConfig); err != nil { 368 return nil, err 369 } 370 relabed := relabel.Process(labels.FromMap(util.ModelLabelSetToMap(lbs)), relabelConfig...) 371 return model.LabelSet(util.LabelsToMetric(relabed)), nil 372 } 373 374 func parseBoolean(key string, logCtx logger.Info, defaultValue bool) (bool, error) { 375 value, ok := logCtx.Config[key] 376 if !ok || value == "" { 377 return defaultValue, nil 378 } 379 b, err := strconv.ParseBool(value) 380 if err != nil { 381 return false, err 382 } 383 return b, nil 384 } 385 386 // loadConfig read YAML-formatted config from filename into cfg. 387 func loadConfig(filename string, cfg interface{}) error { 388 buf, err := ioutil.ReadFile(filename) 389 if err != nil { 390 return errors.Wrap(err, "Error reading config file") 391 } 392 393 return yaml.UnmarshalStrict(buf, cfg) 394 }