Platform Foundations
Flyte provides a robust set of foundational utilities in flytestdlib that handle configuration, logging, and metrics. These utilities are designed to be context-aware, allowing metadata to flow seamlessly from the entry point of a request down to the lowest-level infrastructure calls.
Hierarchical Configuration Management
Flyte uses a hierarchical configuration system built on top of Viper. The core of this system is the config.Section interface, which allows different components to register their own typed configuration structures.
Section Registration
Components register their configuration during initialization using config.MustRegisterSection or config.MustRegisterSectionWithUpdates. This registration creates a globally accessible registry of configuration sections.
// From flytestdlib/logger/config.go
var (
defaultConfig = &Config{
Formatter: FormatterConfig{
Type: FormatterJSON,
},
Level: WarnLevel,
}
configSection = config.MustRegisterSectionWithUpdates(configSectionKey, defaultConfig, func(ctx context.Context, newValue config.Config) {
onConfigUpdated(*newValue.(*Config))
})
)
The MustRegisterSectionWithUpdates function is particularly powerful as it allows components to react to configuration changes at runtime. When the underlying configuration file or environment variable changes, the provided callback is executed, enabling dynamic updates like changing log levels without restarting the service.
Viper Integration
The viperAccessor in flytestdlib/config/viper/viper.go implements the config.Accessor interface. It manages the complexity of loading configuration from multiple sources:
- Command-line flags: Integrated via
pflag. - Environment variables: Automatically bound to configuration keys (e.g.,
LOGGER_LEVEL). - Configuration files: Supports multiple search paths and file formats (YAML, JSON, etc.).
The system also supports a "strict mode" which ensures that all keys in the configuration file correspond to a registered section, preventing silent typos in configuration files.
Context-Aware Logging
Logging in Flyte is handled by the logger package, which provides a wrapper around logrus. The primary design goal is to ensure that logs are enriched with metadata from the context.Context.
Metadata Extraction
The getLogger function extracts fields from the context using contextutils.GetLogFields(ctx). This ensures that if a request is associated with a specific project, domain, or workflow ID, those fields are automatically included in every log message generated within that context.
// From flytestdlib/logger/logger.go
func getLogger(ctx context.Context) logrus.FieldLogger {
cfg := GetConfig()
if cfg.Mute {
return noopLogger
}
entry := logrus.WithFields(logrus.Fields(contextutils.GetLogFields(ctx)))
if cfg.IncludeSourceCode {
entry = entry.WithField(sourceCodeKey, getSourceLocation())
}
entry.Level = logrus.Level(cfg.Level)
return entry
}
Dynamic Configuration
The logger supports multiple formatters (JSON, Text, and GCP-optimized) and dynamic level updates. The onConfigUpdated handler reconfigures the global logrus instance whenever the Logger configuration section is updated.
func onConfigUpdated(cfg Config) {
logrus.SetLevel(logrus.Level(cfg.Level))
switch cfg.Formatter.Type {
case FormatterGCP:
if _, isGCP := logrus.StandardLogger().Formatter.(*GcpFormatter); !isGCP {
logrus.SetFormatter(&GcpFormatter{})
}
// ... other formatters
}
}
Scoped and Labeled Metrics
Flyte uses Prometheus for metrics, but adds layers of abstraction in flytestdlib/promutils to simplify instrumentation and ensure consistent naming.
Metric Scoping
The promutils.Scope interface manages metric name prefixing. This prevents name collisions between different components and allows for hierarchical organization of metrics.
// From flytestdlib/promutils/scope.go
func (m metricsScope) NewSubScope(subscopeName string) Scope {
if !strings.HasSuffix(subscopeName, defaultScopeDelimiterStr) {
subscopeName += defaultScopeDelimiterStr
}
return NewScope(m.scope + subscopeName)
}
When a component creates a metric using a scope, the scope's prefix is automatically prepended to the metric name. For example, a scope named flyte:admin: creating a counter named requests_total will result in a Prometheus metric named flyte_admin_requests_total.
Labeled Metrics
The promutils.labeled package provides high-level utilities that automatically extract label values from the context.Context. This is essential for multi-tenant services where metrics need to be sliced by project or domain.
// From flytestdlib/promutils/labeled/counter.go
func (c Counter) Inc(ctx context.Context) {
counter, err := c.GetMetricWith(contextutils.Values(ctx, c.labels...))
if err != nil {
panic(err.Error())
}
counter.Inc()
}
By using labeled.Counter, developers don't need to manually pass label values every time they increment a counter; the values are pulled from the context based on a pre-configured set of keys.
Platform Health and Versioning
Flyte includes utilities for monitoring the health and version of the running binary.
Versioning
The version package allows injecting build information (Git SHA, Version, Build Time) at compile time using ldflags. This information can be logged on startup and exposed via internal health endpoints.
// From flytestdlib/version/version.go
func LogBuildInformation(appName string) {
logrus.Info("------------------------------------------------------------------------")
msg := fmt.Sprintf("App [%s], Version [%s], BuildSHA [%s], BuildTS [%s]", appName, Version, Build, BuildTime)
logrus.Info(msg)
logrus.Info("------------------------------------------------------------------------")
}
Profiling
Internal health monitoring is often complemented by the profutils package (typically used in conjunction with version), which can start a profiling server to expose standard Go pprof handlers, allowing for deep performance analysis of running Flyte components.