| /** |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| package logcfg |
| |
| import ( |
| "bytes" |
| "fmt" |
| "io" |
| "io/ioutil" |
| "os" |
| "path/filepath" |
| "sort" |
| "strings" |
| |
| log "github.com/sirupsen/logrus" |
| "github.com/spf13/cast" |
| |
| "mynewt.apache.org/newt/newt/newtutil" |
| "mynewt.apache.org/newt/newt/pkg" |
| "mynewt.apache.org/newt/newt/syscfg" |
| "mynewt.apache.org/newt/newt/val" |
| "mynewt.apache.org/newt/util" |
| ) |
| |
| const HEADER_PATH = "logcfg/logcfg.h" |
| |
| type Log struct { |
| // Log name; equal to the name of the YAML map that defines the log. |
| Name string |
| |
| // The package that defines the log. |
| Source *pkg.LocalPackage |
| |
| // The log's numeric module ID. |
| Module val.ValSetting |
| |
| // The level assigned to this log. |
| Level val.ValSetting |
| |
| // The log's string name |
| NameStr string |
| } |
| |
| // Map of: [log-name] => log |
| type LogMap map[string]Log |
| |
| // The log configuration of the target. |
| type LCfg struct { |
| // [log-name] => log |
| Logs LogMap |
| |
| // Strings describing errors encountered while parsing the log config. |
| InvalidSettings []string |
| |
| // Contains sets of logs with conflicting module IDs. |
| // [module-ID] => <slice-of-logs-with-module-id> |
| ModuleConflicts map[int][]Log |
| } |
| |
| // Maps numeric log levels to their string representations. Used when |
| // generating the C log macros. |
| var logLevelNames = []string{ |
| 0: "DEBUG", |
| 1: "INFO", |
| 2: "WARN", |
| 3: "ERROR", |
| 4: "CRITICAL", |
| 15: "DISABLED", |
| } |
| |
| func LogLevelString(level int) string { |
| if level < 0 || level >= len(logLevelNames) { |
| return "???" |
| } |
| |
| return logLevelNames[level] |
| } |
| |
| func NewLCfg() LCfg { |
| return LCfg{ |
| Logs: map[string]Log{}, |
| ModuleConflicts: map[int][]Log{}, |
| } |
| } |
| |
| // Parses a single log definition from a YAML map. The `logMapItf` parameter |
| // should be a map with the following elements: |
| // "module": <module-string> |
| // "level": <level-string> |
| func parseOneLog(name string, lpkg *pkg.LocalPackage, logMapItf interface{}, |
| cfg *syscfg.Cfg) (Log, error) { |
| |
| cl := Log{ |
| Name: name, |
| Source: lpkg, |
| } |
| |
| logMap := cast.ToStringMapString(logMapItf) |
| if logMap == nil { |
| return cl, util.FmtNewtError( |
| "\"%s\" missing required field \"module\"", name) |
| } |
| |
| modStr := logMap["module"] |
| if modStr == "" { |
| return cl, util.FmtNewtError( |
| "\"%s\" missing required field \"module\"", name) |
| } |
| mod, err := val.ResolveValSetting(modStr, cfg) |
| if err != nil { |
| return cl, util.FmtNewtError( |
| "\"%s\" contains invalid \"module\": %s", |
| name, err.Error()) |
| } |
| if _, err := mod.IntVal(); err != nil { |
| return cl, util.FmtNewtError( |
| "\"%s\" contains invalid \"module\": %s", name, err.Error()) |
| } |
| |
| levelStr := logMap["level"] |
| if levelStr == "" { |
| return cl, util.FmtNewtError( |
| "\"%s\" missing required field \"level\"", name) |
| } |
| level, err := val.ResolveValSetting(levelStr, cfg) |
| if err != nil { |
| return cl, util.FmtNewtError( |
| "\"%s\" contains invalid \"level\": %s", |
| name, err.Error()) |
| } |
| if _, err := level.IntVal(); err != nil { |
| return cl, util.FmtNewtError( |
| "\"%s\" contains invalid \"level\": %s", name, err.Error()) |
| } |
| |
| nameStr := logMap["name"] |
| if nameStr == "" { |
| // If there is no "name:" node, use log id without _LOG suffix |
| nameStr = "\"" + strings.Replace(name, "_LOG", "", 1) + "\"" |
| } |
| |
| cl.Module = mod |
| cl.Level = level |
| cl.NameStr = nameStr |
| |
| return cl, nil |
| } |
| |
| // Reads all the logs defined by the specified package. The log definitions |
| // are read from the `syscfg.logs` map in the package's `syscfg.yml` file. |
| func (lcfg *LCfg) readOnePkg(lpkg *pkg.LocalPackage, cfg *syscfg.Cfg) { |
| lsettings := cfg.AllSettingsForLpkg(lpkg) |
| logMaps, err := lpkg.SyscfgY.GetValStringMap("syscfg.logs", lsettings) |
| util.OneTimeWarningError(err) |
| |
| for name, logMapItf := range logMaps { |
| cl, err := parseOneLog(name, lpkg, logMapItf, cfg) |
| if err != nil { |
| lcfg.InvalidSettings = |
| append(lcfg.InvalidSettings, strings.TrimSpace(err.Error())) |
| } else { |
| lcfg.Logs[cl.Name] = cl |
| } |
| } |
| } |
| |
| // Searches the log configuration for logs with identical module IDs. The log |
| // configuration object is populated with the results. |
| func (lcfg *LCfg) detectModuleConflicts() { |
| m := map[int][]Log{} |
| |
| for _, l := range lcfg.Logs { |
| intMod, _ := l.Module.IntVal() |
| m[intMod] = append(m[intMod], l) |
| } |
| |
| for mod, logs := range m { |
| if len(logs) > 1 { |
| for _, l := range logs { |
| lcfg.ModuleConflicts[mod] = |
| append(lcfg.ModuleConflicts[mod], l) |
| } |
| } |
| } |
| } |
| |
| // Reads all log definitions for each of the specified packages. The |
| // returned LCfg object is populated with the result of this operation. |
| func Read(lpkgs []*pkg.LocalPackage, cfg *syscfg.Cfg) LCfg { |
| lcfg := NewLCfg() |
| |
| for _, lpkg := range lpkgs { |
| lcfg.readOnePkg(lpkg, cfg) |
| } |
| |
| lcfg.detectModuleConflicts() |
| |
| return lcfg |
| } |
| |
| // If any errors were encountered while parsing log definitions, this function |
| // returns a string indicating the errors. If no errors were encountered, "" |
| // is returned. |
| func (lcfg *LCfg) ErrorText() string { |
| str := "" |
| |
| if len(lcfg.InvalidSettings) > 0 { |
| str += "Invalid log definitions detected:" |
| for _, e := range lcfg.InvalidSettings { |
| str += "\n " + e |
| } |
| } |
| |
| if len(lcfg.ModuleConflicts) > 0 { |
| str += "Log module conflicts detected:\n" |
| for mod, logs := range lcfg.ModuleConflicts { |
| for _, l := range logs { |
| str += fmt.Sprintf(" Module=%d Log=%s Package=%s\n", |
| mod, l.Name, l.Source.FullName()) |
| } |
| } |
| |
| str += |
| "\nResolve the problem by assigning unique module IDs to each log." |
| } |
| |
| return str |
| } |
| |
| // Retrieves a sorted slice of logs from the receiving log configuration. |
| func (lcfg *LCfg) sortedLogs() []Log { |
| names := make([]string, 0, len(lcfg.Logs)) |
| |
| for n, _ := range lcfg.Logs { |
| names = append(names, n) |
| } |
| sort.Strings(names) |
| |
| logs := make([]Log, 0, len(names)) |
| for _, n := range names { |
| logs = append(logs, lcfg.Logs[n]) |
| } |
| |
| return logs |
| } |
| |
| // Writes a no-op stub log C macro definition. |
| func writeLogStub(logName string, levelStr string, w io.Writer) { |
| fmt.Fprintf(w, "#define %s_%s(...) IGNORE(__VA_ARGS__)\n", |
| logName, levelStr) |
| } |
| |
| // Writes a log C macro definition. |
| func writeLogMacro(logName string, module int, levelStr string, w io.Writer) { |
| fmt.Fprintf(w, |
| "#define %s_%s(...) MODLOG_%s(%d, __VA_ARGS__)\n", |
| logName, levelStr, levelStr, module) |
| } |
| |
| // Write log C macro definitions for each log in the log configuration. |
| func (lcfg *LCfg) writeLogMacros(w io.Writer) { |
| logs := lcfg.sortedLogs() |
| for _, l := range logs { |
| fmt.Fprintf(w, "\n") |
| |
| levelInt, _ := util.AtoiNoOct(l.Level.Value) |
| for i, levelStr := range logLevelNames { |
| if levelStr != "" { |
| if i < levelInt { |
| writeLogStub(l.Name, levelStr, w) |
| } else { |
| modInt, _ := l.Module.IntVal() |
| writeLogMacro(l.Name, modInt, levelStr, w) |
| } |
| } |
| } |
| } |
| } |
| |
| // Write log C macro definitions for each log in the log configuration. |
| func (lcfg *LCfg) writeLogModuleNames(w io.Writer) { |
| logs := lcfg.sortedLogs() |
| fmt.Fprintf(w, "\n") |
| for _, l := range logs { |
| fmt.Fprintf(w, |
| "#define %s_NAME %s\n", |
| l.Name, l.NameStr) |
| } |
| } |
| |
| func (lcfg *LCfg) writeLogModuleCases(w io.Writer) { |
| logs := lcfg.sortedLogs() |
| for _, l := range logs { |
| fmt.Fprintf(w, |
| " case %s: return %s_NAME;\n", |
| l.Module.Value, l.Name) |
| } |
| } |
| |
| // Writes a logcfg header file to the specified writer. |
| func (lcfg *LCfg) writeHeader(w io.Writer) { |
| fmt.Fprintf(w, newtutil.GeneratedPreamble()) |
| |
| fmt.Fprintf(w, "#ifndef H_MYNEWT_LOGCFG_\n") |
| fmt.Fprintf(w, "#define H_MYNEWT_LOGCFG_\n\n") |
| |
| if len(lcfg.Logs) > 0 { |
| fmt.Fprintf(w, "#include \"modlog/modlog.h\"\n") |
| fmt.Fprintf(w, "#include \"log_common/log_common.h\"\n") |
| |
| lcfg.writeLogMacros(w) |
| lcfg.writeLogModuleNames(w) |
| fmt.Fprintf(w, "\n") |
| } |
| fmt.Fprintf(w, "const char *logcfg_log_module_name(uint8_t id);\n\n") |
| |
| fmt.Fprintf(w, "#endif\n") |
| } |
| |
| // Writes a logcfg src file to the specified writer. |
| func (lcfg *LCfg) writeSource(w io.Writer) { |
| fmt.Fprintf(w, newtutil.GeneratedPreamble()) |
| |
| fmt.Fprintf(w, "#include <stdint.h>\n") |
| fmt.Fprintf(w, "#include <stdlib.h>\n") |
| fmt.Fprintf(w, "#include <logcfg/logcfg.h>\n\n") |
| fmt.Fprintf(w, "const char *\nlogcfg_log_module_name(uint8_t id)\n{\n") |
| fmt.Fprintf(w, " switch (id) {\n") |
| lcfg.writeLogModuleCases(w) |
| fmt.Fprintf(w, " default: return NULL;\n }\n}\n") |
| } |
| |
| // Ensures an up-to-date logcfg header is written for the target. |
| func (lcfg *LCfg) EnsureWritten(includeDir string, srcDir string, targetName string) error { |
| buf := bytes.Buffer{} |
| srcBuf := bytes.Buffer{} |
| lcfg.writeHeader(&buf) |
| lcfg.writeSource(&srcBuf) |
| |
| path := includeDir + "/" + HEADER_PATH |
| |
| writeReqd, err := util.FileContentsChanged(path, buf.Bytes()) |
| if err != nil { |
| return err |
| } |
| if writeReqd { |
| log.Debugf("logcfg changed; writing header file (%s).", path) |
| |
| if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { |
| return util.NewNewtError(err.Error()) |
| } |
| |
| if err := ioutil.WriteFile(path, buf.Bytes(), 0644); err != nil { |
| return util.NewNewtError(err.Error()) |
| } |
| } else { |
| log.Debugf("logcfg unchanged; not writing header file (%s).", path) |
| } |
| |
| path = fmt.Sprintf("%s/%s-logcfg.c", srcDir, targetName) |
| writeReqd, err = util.FileContentsChanged(path, srcBuf.Bytes()) |
| |
| if writeReqd { |
| log.Debugf("logcfg changed; writing source file (%s).", path) |
| if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { |
| return util.NewNewtError(err.Error()) |
| } |
| if err := os.WriteFile(path, srcBuf.Bytes(), 0644); err != nil { |
| return util.NewNewtError(err.Error()) |
| } |
| } |
| return nil |
| } |