Merge Go frontend (apiserver + xcecli) into meson build
This commit is contained in:
parent
43dc12a36c
commit
c9dc8c4676
205 changed files with 30793 additions and 328 deletions
210
src/frontend/xcecli/config/config.go
Executable file
210
src/frontend/xcecli/config/config.go
Executable file
|
|
@ -0,0 +1,210 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/user"
|
||||
"path"
|
||||
"sort"
|
||||
"time"
|
||||
)
|
||||
|
||||
type APIServerInfo struct {
|
||||
// Name string `json:"name"`
|
||||
URL string `json:"url"`
|
||||
Token string `json:"token"`
|
||||
Path string `json:"path"`
|
||||
CreateTime int64 `json:"createtime"`
|
||||
CACert string `json:"cacert,omitempty"`
|
||||
TokenExpireTime int64 `json:"token_expire_time"`
|
||||
//Version string `json:"version,omitempty"`
|
||||
}
|
||||
|
||||
// APIServerInfoArray is used to sort the APIServerInfo by CreateTime
|
||||
type APIServerInfoArray []APIServerInfo
|
||||
|
||||
func (a APIServerInfoArray) Len() int { return len(a) }
|
||||
func (a APIServerInfoArray) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a APIServerInfoArray) Less(i, j int) bool { return a[i].CreateTime > a[j].CreateTime }
|
||||
|
||||
type Config struct {
|
||||
APIServerInfo APIServerInfoArray `json:"servers"`
|
||||
}
|
||||
|
||||
// ConfigManager is used to manage the config
|
||||
type ConfigManager struct {
|
||||
config *Config
|
||||
configPath string
|
||||
}
|
||||
|
||||
// NewConfigManager is used to create the config manager
|
||||
func NewConfigManager() (*ConfigManager, error) {
|
||||
usr, err := user.Current()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get the user info: [%v]", err)
|
||||
}
|
||||
|
||||
configDir := path.Join(usr.HomeDir, ".xce")
|
||||
configPath := path.Join(configDir, "config.json")
|
||||
|
||||
// create the config directory if it does not exist
|
||||
if err := os.MkdirAll(configDir, 0700); err != nil {
|
||||
return nil, fmt.Errorf("failed to create the config directory: [%v]", err)
|
||||
}
|
||||
|
||||
cm := &ConfigManager{
|
||||
configPath: configPath,
|
||||
}
|
||||
|
||||
return cm, nil
|
||||
}
|
||||
|
||||
// GetConfig is used to get the config
|
||||
func (cm *ConfigManager) GetConfig() (*Config, error) {
|
||||
if cm == nil {
|
||||
return nil, fmt.Errorf("the config manager is not initialized")
|
||||
}
|
||||
|
||||
if cm.config != nil {
|
||||
return cm.config, nil
|
||||
}
|
||||
|
||||
config, err := cm.loadConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cm.config = config
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// SaveConfig is used to save the config
|
||||
func (cm *ConfigManager) SaveConfig(config *Config) error {
|
||||
// sort the APIServerInfoArray by CreateTime
|
||||
sort.Sort(config.APIServerInfo)
|
||||
data, err := json.MarshalIndent(config, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dir := path.Dir(cm.configPath)
|
||||
if err := os.MkdirAll(dir, 0700); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := os.WriteFile(cm.configPath, data, 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cm.config = config
|
||||
return nil
|
||||
}
|
||||
|
||||
// loadConfig is used to load the config from the file
|
||||
func (cm *ConfigManager) loadConfig() (*Config, error) {
|
||||
// if the config file does not exist, create the default config
|
||||
if _, err := os.Stat(cm.configPath); os.IsNotExist(err) {
|
||||
defaultConfig := &Config{
|
||||
APIServerInfo: make([]APIServerInfo, 0),
|
||||
}
|
||||
// save the default config
|
||||
if err := cm.SaveConfig(defaultConfig); err != nil {
|
||||
return nil, fmt.Errorf("failed to create the default config: [%v]", err)
|
||||
}
|
||||
return defaultConfig, nil
|
||||
}
|
||||
|
||||
// read the config file
|
||||
data, err := os.ReadFile(cm.configPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read the config file: [%v]", err)
|
||||
}
|
||||
|
||||
// parse the config file
|
||||
var config Config
|
||||
if err := json.Unmarshal(data, &config); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse the config file: [%s,%v]", cm.configPath, err)
|
||||
}
|
||||
|
||||
return &config, nil
|
||||
}
|
||||
|
||||
// Save is used to save the config
|
||||
func (c *Config) Save() error {
|
||||
usr, err := user.Current()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// get the config directory
|
||||
configDir := path.Join(usr.HomeDir, ".xce")
|
||||
configPath := path.Join(configDir, "config.json")
|
||||
|
||||
// create the config directory if it does not exist
|
||||
if err := os.MkdirAll(configDir, 0700); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// marshal the config
|
||||
data, err := json.MarshalIndent(c, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// write the config to the file
|
||||
return os.WriteFile(configPath, data, 0600)
|
||||
}
|
||||
|
||||
// IsAuthenticated check if the user has been authenticated by the API server
|
||||
// if not, return an error
|
||||
func (cm *ConfigManager) IsAuthenticated() (*APIServerInfo, error) {
|
||||
cfg, err := cm.GetConfig()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get config: [%v]", err)
|
||||
}
|
||||
|
||||
if len(cfg.APIServerInfo) == 0 {
|
||||
return nil, fmt.Errorf("you are not logged in, please log in first")
|
||||
}
|
||||
|
||||
serverInfo := &cfg.APIServerInfo[0]
|
||||
if serverInfo.URL == "" {
|
||||
return nil, fmt.Errorf("no default API Server is set, please set a default server")
|
||||
}
|
||||
|
||||
if serverInfo.Token == "" {
|
||||
return nil, fmt.Errorf("you are not logged in, please log in first")
|
||||
}
|
||||
|
||||
// Use the token expire time from server configuration
|
||||
expireTime := serverInfo.TokenExpireTime
|
||||
if expireTime == 0 {
|
||||
expireTime = 86400 // Default to 24 hours if not set
|
||||
}
|
||||
if time.Now().Unix()-serverInfo.CreateTime > expireTime {
|
||||
return nil, fmt.Errorf("your login has expired, please log in again")
|
||||
}
|
||||
|
||||
return serverInfo, nil
|
||||
}
|
||||
|
||||
// GetServerInfo
|
||||
func (cm *ConfigManager) GetServerInfo() (*APIServerInfo, error) {
|
||||
cfg, err := cm.GetConfig()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get config: [%v]", err)
|
||||
}
|
||||
|
||||
if len(cfg.APIServerInfo) == 0 || cfg.APIServerInfo[0].URL == "" {
|
||||
return nil, fmt.Errorf("not found the API Server information")
|
||||
}
|
||||
|
||||
return &cfg.APIServerInfo[0], nil
|
||||
}
|
||||
|
||||
// NewConfigManagerWithPath creates a ConfigManager using a specific config file path.
|
||||
// Used in tests.
|
||||
func NewConfigManagerWithPath(path string) *ConfigManager {
|
||||
return &ConfigManager{configPath: path}
|
||||
}
|
||||
189
src/frontend/xcecli/config/logger.go
Executable file
189
src/frontend/xcecli/config/logger.go
Executable file
|
|
@ -0,0 +1,189 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
"gopkg.in/natefinch/lumberjack.v2"
|
||||
)
|
||||
|
||||
var (
|
||||
Log *zap.Logger
|
||||
)
|
||||
|
||||
const (
|
||||
defaultLoggerConfigFile = "logger.yaml"
|
||||
defaultLoggerOutputFile = "xcecli_out.log"
|
||||
)
|
||||
|
||||
type LoggerConfig struct {
|
||||
Logging struct {
|
||||
Level string `yaml:"level"`
|
||||
Encoding string `yaml:"encoding"`
|
||||
OutputPath string `yaml:"outputPath"`
|
||||
File struct {
|
||||
MaxSize int `yaml:"maxSize"`
|
||||
MaxBackups int `yaml:"maxBackups"`
|
||||
MaxAge int `yaml:"maxAge"`
|
||||
Compress bool `yaml:"compress"`
|
||||
} `yaml:"file"`
|
||||
EncoderConfig struct {
|
||||
MessageKey string `yaml:"messageKey"`
|
||||
LevelKey string `yaml:"levelKey"`
|
||||
TimeKey string `yaml:"timeKey"`
|
||||
NameKey string `yaml:"nameKey"`
|
||||
CallerKey string `yaml:"callerKey"`
|
||||
FunctionKey string `yaml:"functionKey"`
|
||||
StacktraceKey string `yaml:"stacktraceKey"`
|
||||
EncodeTime string `yaml:"timeEncoder"`
|
||||
EncodeLevel string `yaml:"levelEncoder"`
|
||||
} `yaml:"encoderConfig"`
|
||||
} `yaml:"logging"`
|
||||
}
|
||||
|
||||
func InitLogger() error {
|
||||
var err error
|
||||
err = loadLoggerConfig()
|
||||
return err
|
||||
}
|
||||
|
||||
func loadLoggerConfig() error {
|
||||
usr, err := user.Current()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get the user info: [%v]", err)
|
||||
}
|
||||
// read logger configuration file
|
||||
configPath := filepath.Join(usr.HomeDir, ".xce")
|
||||
|
||||
var loggerConfig LoggerConfig
|
||||
v := viper.New()
|
||||
|
||||
v.SetDefault("logging.level", "info") // debug, info, warn, error, fatal
|
||||
v.SetDefault("logging.encoding", "console") // console, json
|
||||
v.SetDefault("logging.outputPath", defaultLoggerOutputFile) // xcecli_out.log
|
||||
v.SetDefault("logging.file.maxSize", 100) // 100MB
|
||||
v.SetDefault("logging.file.maxBackups", 10) // 10
|
||||
v.SetDefault("logging.file.maxAge", 14) // 14 days
|
||||
v.SetDefault("logging.file.compress", true) // true
|
||||
v.SetDefault("logging.encoderConfig.messageKey", "message") // message
|
||||
v.SetDefault("logging.encoderConfig.levelKey", "level") // level
|
||||
v.SetDefault("logging.encoderConfig.timeKey", "timestamp") // timestamp
|
||||
v.SetDefault("logging.encoderConfig.nameKey", "logger") // logger
|
||||
v.SetDefault("logging.encoderConfig.callerKey", "caller") // caller
|
||||
v.SetDefault("logging.encoderConfig.functionKey", "func") // func
|
||||
v.SetDefault("logging.encoderConfig.stacktraceKey", "stacktrace") // stacktrace
|
||||
v.SetDefault("logging.encoderConfig.timeEncoder", "ISO8601") // ISO8601
|
||||
v.SetDefault("logging.encoderConfig.levelEncoder", "capital") // capital
|
||||
v.SetConfigName(defaultLoggerConfigFile)
|
||||
v.SetConfigType("yaml")
|
||||
v.AddConfigPath(configPath)
|
||||
// yamlContent := `# log level: debug, info, warn, error, fatal
|
||||
// # debug: detailed log
|
||||
// # info: normal log
|
||||
// # warn: warning log
|
||||
// # error: error log
|
||||
// # fatal: fatal log` + v.GetString("logging.level")
|
||||
|
||||
err = v.ReadInConfig()
|
||||
if err != nil {
|
||||
os.MkdirAll(configPath, 0755)
|
||||
|
||||
if err = v.WriteConfigAs(filepath.Join(configPath, "logger.yaml")); err != nil {
|
||||
fmt.Printf("failed to write default logger configuration file: [%v]", err)
|
||||
}
|
||||
}
|
||||
err = v.Unmarshal(&loggerConfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to decode into struct: [%v]", err)
|
||||
}
|
||||
// Handle both formats for OutputPath
|
||||
if !filepath.IsAbs(loggerConfig.Logging.OutputPath) {
|
||||
if loggerConfig.Logging.OutputPath == "" {
|
||||
loggerConfig.Logging.OutputPath = defaultLoggerOutputFile
|
||||
}
|
||||
// If OutputPath is relative, prepend the logs directory
|
||||
loggerConfig.Logging.OutputPath = filepath.Join(configPath, "logs", loggerConfig.Logging.OutputPath)
|
||||
}
|
||||
logDir := filepath.Dir(loggerConfig.Logging.OutputPath) // Extract the directory
|
||||
// Create the directory if it does not exist
|
||||
if err = os.MkdirAll(logDir, 0755); err != nil {
|
||||
return fmt.Errorf("failed to create client logs directory: %w", err)
|
||||
}
|
||||
Log, err = createLogger(loggerConfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create client logger: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func createLogger(loggerConfig LoggerConfig) (*zap.Logger, error) {
|
||||
configLevel, err := zapcore.ParseLevel(loggerConfig.Logging.Level)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// create log file and set the permission
|
||||
file, err := os.OpenFile(loggerConfig.Logging.OutputPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
file.Close()
|
||||
|
||||
// configure lumberjack for log rotation
|
||||
logWriter := &lumberjack.Logger{
|
||||
Filename: loggerConfig.Logging.OutputPath,
|
||||
MaxSize: loggerConfig.Logging.File.MaxSize,
|
||||
MaxBackups: loggerConfig.Logging.File.MaxBackups,
|
||||
MaxAge: loggerConfig.Logging.File.MaxAge,
|
||||
Compress: loggerConfig.Logging.File.Compress,
|
||||
}
|
||||
|
||||
// Create cores for different log levels
|
||||
core := zapcore.NewCore(
|
||||
zapcore.NewConsoleEncoder(zapcore.EncoderConfig{
|
||||
MessageKey: loggerConfig.Logging.EncoderConfig.MessageKey,
|
||||
LevelKey: loggerConfig.Logging.EncoderConfig.LevelKey,
|
||||
TimeKey: loggerConfig.Logging.EncoderConfig.TimeKey,
|
||||
NameKey: loggerConfig.Logging.EncoderConfig.NameKey,
|
||||
CallerKey: loggerConfig.Logging.EncoderConfig.CallerKey,
|
||||
StacktraceKey: "", // disable stacktrace
|
||||
EncodeTime: zapcore.ISO8601TimeEncoder,
|
||||
EncodeLevel: zapcore.CapitalLevelEncoder,
|
||||
EncodeCaller: zapcore.ShortCallerEncoder,
|
||||
ConsoleSeparator: " ",
|
||||
}),
|
||||
zapcore.AddSync(logWriter),
|
||||
configLevel,
|
||||
)
|
||||
logger := zap.New(core,
|
||||
zap.AddCaller(), // caller info
|
||||
zap.AddStacktrace(zap.ErrorLevel), // show error stacktrace
|
||||
zap.AddCallerSkip(1), // skip one layer of caller
|
||||
)
|
||||
return logger, nil
|
||||
|
||||
}
|
||||
|
||||
func Info(msg string, fields ...zap.Field) {
|
||||
Log.WithOptions(zap.WithCaller(false)).Info(msg, fields...)
|
||||
}
|
||||
func Error(msg string, fields ...zap.Field) {
|
||||
Log.WithOptions(zap.WithCaller(false)).Error(msg, fields...)
|
||||
}
|
||||
|
||||
func Warn(msg string, fields ...zap.Field) {
|
||||
Log.WithOptions(zap.WithCaller(false)).Warn(msg, fields...)
|
||||
}
|
||||
|
||||
func Debug(msg string, fields ...zap.Field) {
|
||||
Log.WithOptions(zap.WithCaller(true)).Debug(msg, fields...)
|
||||
}
|
||||
|
||||
func Fatal(msg string, fields ...zap.Field) {
|
||||
Log.WithOptions(zap.WithCaller(false)).Fatal(msg, fields...)
|
||||
}
|
||||
26
src/frontend/xcecli/config/logger.yaml
Executable file
26
src/frontend/xcecli/config/logger.yaml
Executable file
|
|
@ -0,0 +1,26 @@
|
|||
logging:
|
||||
# Log level: debug, info, warn, error, fatal
|
||||
level: "info"
|
||||
# Log encoding: json or console
|
||||
encoding: console
|
||||
|
||||
# standard output
|
||||
outputPath: "xcecli_out.log"
|
||||
# File rotation settings
|
||||
file:
|
||||
maxSize: 100 # megabytes before rotation
|
||||
maxBackups: 10 # number of old files to keep
|
||||
maxAge: 14 # days to keep old files
|
||||
compress: true # compress rotated files
|
||||
|
||||
# Encoder configuration
|
||||
encoderConfig:
|
||||
messageKey: "message"
|
||||
levelKey: "level"
|
||||
timeKey: "timestamp"
|
||||
nameKey: "logger"
|
||||
callerKey: "caller"
|
||||
functionKey: "func"
|
||||
stacktraceKey: "stacktrace"
|
||||
timeEncoder: "ISO8601"
|
||||
levelEncoder: "capital"
|
||||
92
src/frontend/xcecli/config/schema.go
Executable file
92
src/frontend/xcecli/config/schema.go
Executable file
|
|
@ -0,0 +1,92 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/xeipuuv/gojsonschema"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
// validate the specified yaml file format whether is correct
|
||||
// return the content of the yaml file if the format is correct
|
||||
// return error if the format is incorrect
|
||||
func ValidateYamlFormat(yamlFilePath string) ([]byte, error) {
|
||||
// find the file path
|
||||
yamlFilePath, err := filepath.Abs(yamlFilePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// get scheme file path, suppose schema file is in the same directory as yaml file
|
||||
yamlFileRootPath := filepath.Dir(yamlFilePath)
|
||||
realFileName := filepath.Base(yamlFilePath)
|
||||
preRealFileName := realFileName[:len(realFileName)-5] + "_schema.json"
|
||||
schemaFilePath := filepath.Join(yamlFileRootPath, "schema", preRealFileName)
|
||||
// checking if the schema file exists
|
||||
if _, err := os.Stat(schemaFilePath); os.IsNotExist(err) {
|
||||
err = fmt.Errorf("schema file %s does not exist ", schemaFilePath)
|
||||
return nil, err
|
||||
}
|
||||
data, err := ValidateYAML(yamlFilePath, schemaFilePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// ValidateYAML validates the YAML content against the provided JSON schema
|
||||
func ValidateYAML(yamlPath, schemaPath string) ([]byte, error) {
|
||||
// schemaLoader is a absolute file path
|
||||
schemaLoader := gojsonschema.NewReferenceLoader("file:///" + filepath.ToSlash(schemaPath))
|
||||
// get yaml file content by reading the file
|
||||
data, err := os.ReadFile(yamlPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// get the yaml file into object
|
||||
var raw interface{}
|
||||
err = yaml.Unmarshal(data, &raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// transform into map[string]interface{}
|
||||
yamlContent := convertYAML(raw)
|
||||
|
||||
// create documentLoader
|
||||
documentLoader := gojsonschema.NewGoLoader(yamlContent)
|
||||
result, err := gojsonschema.Validate(schemaLoader, documentLoader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !result.Valid() {
|
||||
var errorMessages string
|
||||
for _, desc := range result.Errors() {
|
||||
errorMessages += fmt.Sprintf("- %s\n", desc)
|
||||
}
|
||||
err = errors.New(errorMessages)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// return the yaml content
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// convertYAML recursively converts map[interface{}]interface{} to map[string]interface{}
|
||||
func convertYAML(i interface{}) interface{} {
|
||||
switch x := i.(type) {
|
||||
case map[interface{}]interface{}:
|
||||
m2 := make(map[string]interface{})
|
||||
for k, v := range x {
|
||||
m2[fmt.Sprintf("%v", k)] = convertYAML(v)
|
||||
}
|
||||
return m2
|
||||
case []interface{}:
|
||||
for idx, v := range x {
|
||||
x[idx] = convertYAML(v)
|
||||
}
|
||||
}
|
||||
return i
|
||||
}
|
||||
88
src/frontend/xcecli/config/schema/logger_schema.json
Executable file
88
src/frontend/xcecli/config/schema/logger_schema.json
Executable file
|
|
@ -0,0 +1,88 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Logger Configuration Schema",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"logging": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"level": {
|
||||
"type": "string",
|
||||
"enum": ["debug", "info", "warn", "error", "fatal"]
|
||||
},
|
||||
"encoding": {
|
||||
"type": "string",
|
||||
"enum": ["json", "console"]
|
||||
},
|
||||
"outputPath": {
|
||||
"type": "string"
|
||||
},
|
||||
"file": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"maxSize": {
|
||||
"type": "number",
|
||||
"description": "megabytes before rotation"
|
||||
},
|
||||
"maxBackups": {
|
||||
"type": "number",
|
||||
"description": "number of old files to keep"
|
||||
},
|
||||
"maxAge": {
|
||||
"type": "number",
|
||||
"description": "days to keep old files"
|
||||
},
|
||||
"compress": {
|
||||
"type": "boolean",
|
||||
"description": "compress rotated files"
|
||||
}
|
||||
},
|
||||
"required": ["maxSize", "maxBackups", "maxAge", "compress"]
|
||||
},
|
||||
"encoderConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"messageKey": {
|
||||
"type": "string"
|
||||
},
|
||||
"levelKey": {
|
||||
"type": "string"
|
||||
},
|
||||
"timeKey": {
|
||||
"type": "string"
|
||||
},
|
||||
"nameKey": {
|
||||
"type": "string"
|
||||
},
|
||||
"callerKey": {
|
||||
"type": "string"
|
||||
},
|
||||
"functionKey": {
|
||||
"type": "string"
|
||||
},
|
||||
"stacktraceKey": {
|
||||
"type": "string"
|
||||
},
|
||||
"timeEncoder": {
|
||||
"type": "string"
|
||||
},
|
||||
"levelEncoder": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"messageKey",
|
||||
"levelKey",
|
||||
"timeKey",
|
||||
"nameKey",
|
||||
"callerKey",
|
||||
"functionKey",
|
||||
"stacktraceKey"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": ["level", "encoding", "outputPath", "file", "encoderConfig"]
|
||||
}
|
||||
},
|
||||
"required": ["logging"]
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue