newt/logcfg/logcfg.go (225 lines of code) (raw):
/**
* 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
}
// 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())
}
cl.Module = mod
cl.Level = level
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)
}
}
}
}
}
// Writes a logcfg header file to the specified writer.
func (lcfg *LCfg) write(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)
fmt.Fprintf(w, "\n")
}
fmt.Fprintf(w, "#endif\n")
}
// Ensures an up-to-date logcfg header is written for the target.
func (lcfg *LCfg) EnsureWritten(includeDir string) error {
buf := bytes.Buffer{}
lcfg.write(&buf)
path := includeDir + "/" + HEADER_PATH
writeReqd, err := util.FileContentsChanged(path, buf.Bytes())
if err != nil {
return err
}
if !writeReqd {
log.Debugf("logcfg unchanged; not writing header file (%s).", path)
return nil
}
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())
}
return nil
}