github.com/ethereumproject/go-ethereum@v5.5.2+incompatible/logger/mlog_file.go (about)

     1  // Copyright 2017 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  // File I/O and registry for mlogs.
    18  
    19  package logger
    20  
    21  import (
    22  	"encoding/json"
    23  	"errors"
    24  	"fmt"
    25  	"math/big"
    26  	"os"
    27  	"os/user"
    28  	"path/filepath"
    29  	"strings"
    30  	"sync"
    31  	"time"
    32  
    33  	"github.com/ethereumproject/go-ethereum/common"
    34  	"github.com/ethereumproject/go-ethereum/logger/glog"
    35  )
    36  
    37  type mlogFormatT uint
    38  
    39  const (
    40  	mLOGPlain mlogFormatT = iota + 1
    41  	mLOGKV
    42  	MLOGJSON
    43  )
    44  
    45  var (
    46  	// If non-empty, overrides the choice of directory in which to write logs.
    47  	// See createLogDirs for the full list of possible destinations.
    48  	mLogDir    = new(string)
    49  	mLogFormat = MLOGJSON
    50  
    51  	errMLogComponentUnavailable = errors.New("provided component name is unavailable")
    52  	ErrUnkownMLogFormat         = errors.New("unknown mlog format")
    53  
    54  	// MLogRegistryAvailable contains all available mlog components submitted by any package
    55  	// with MLogRegisterAvailable.
    56  	mLogRegistryAvailable = make(map[mlogComponent][]*MLogT)
    57  	// MLogRegistryActive contains all registered mlog component and their respective loggers.
    58  	mLogRegistryActive = make(map[mlogComponent]*Logger)
    59  	mlogRegLock        sync.RWMutex
    60  
    61  	// Abstract literals (for documentation examples, labels)
    62  	mlogInterfaceExamples = map[string]interface{}{
    63  		"INT":            int(0),
    64  		"BIGINT":         new(big.Int),
    65  		"STRING":         "string",
    66  		"BOOL":           true,
    67  		"QUOTEDSTRING":   "string with spaces",
    68  		"STRING_OR_NULL": nil,
    69  		"DURATION":       time.Minute + time.Second*3 + time.Millisecond*42,
    70  		"OBJECT":         common.GetClientSessionIdentity(),
    71  	}
    72  
    73  	MLogStringToFormat = map[string]mlogFormatT{
    74  		"plain": mLOGPlain,
    75  		"kv":    mLOGKV,
    76  		"json":  MLOGJSON,
    77  	}
    78  
    79  	// Global var set to false if "--mlog=off", used to simply/
    80  	// speed-up checks to avoid performance penalty if mlog is
    81  	// off.
    82  	isMlogEnabled bool
    83  )
    84  
    85  func (f mlogFormatT) String() string {
    86  	switch f {
    87  	case MLOGJSON:
    88  		return "json"
    89  	case mLOGKV:
    90  		return "kv"
    91  	case mLOGPlain:
    92  		return "plain"
    93  	}
    94  	panic(ErrUnkownMLogFormat)
    95  }
    96  
    97  // MLogT defines an mlog LINE
    98  type MLogT struct {
    99  	sync.Mutex
   100  	// TODO: can remove these json tags, since we have a custom MarshalJSON fn
   101  	Description string        `json:"-"`
   102  	Receiver    string        `json:"receiver"`
   103  	Verb        string        `json:"verb"`
   104  	Subject     string        `json:"subject"`
   105  	Details     []MLogDetailT `json:"details"`
   106  }
   107  
   108  // MLogDetailT defines an mlog LINE DETAILS
   109  type MLogDetailT struct {
   110  	Owner string      `json:"owner"`
   111  	Key   string      `json:"key"`
   112  	Value interface{} `json:"value"`
   113  }
   114  
   115  // mlogComponent is used as a golang receiver type that can call Send(logLine).
   116  type mlogComponent string
   117  
   118  // The following vars and init() essentially duplicate those found in glog_file;
   119  // the reason for the non-DRYness of that is that this allows us flexibility
   120  // as we finalize the spec and format for the mlog lines, allowing customization
   121  // of the establish system if desired, without exporting the vars from glog.
   122  var (
   123  	pid      = os.Getpid()
   124  	program  = filepath.Base(os.Args[0])
   125  	host     = "unknownhost"
   126  	userName = "unknownuser"
   127  )
   128  
   129  func init() {
   130  	h, err := os.Hostname()
   131  	if err == nil {
   132  		host = shortHostname(h)
   133  	}
   134  
   135  	current, err := user.Current()
   136  	if err == nil {
   137  		userName = current.Username
   138  	}
   139  
   140  	// Sanitize userName since it may contain filepath separators on Windows.
   141  	userName = strings.Replace(userName, `\`, "_", -1)
   142  }
   143  
   144  func SetMlogEnabled(b bool) {
   145  	isMlogEnabled = b
   146  }
   147  
   148  func MlogEnabled() bool {
   149  	return isMlogEnabled
   150  }
   151  
   152  // MLogRegisterAvailable is called for each log component variable from a package/mlog.go file
   153  // as they set up their mlog vars.
   154  // It registers an mlog component as Available.
   155  func MLogRegisterAvailable(name string, lines []*MLogT) mlogComponent {
   156  	c := mlogComponent(name)
   157  	mlogRegLock.Lock()
   158  	mLogRegistryAvailable[c] = lines
   159  	mlogRegLock.Unlock()
   160  	return c
   161  }
   162  
   163  // GetMlogRegistryAvailable returns copy of all registered components mapping
   164  func GetMLogRegistryAvailable() map[mlogComponent][]*MLogT {
   165  	mlogRegLock.RLock()
   166  	defer mlogRegLock.RUnlock()
   167  
   168  	ret := make(map[mlogComponent][]*MLogT)
   169  	for k, v := range mLogRegistryAvailable {
   170  		ret[k] = make([]*MLogT, len(v))
   171  		copy(ret[k], v)
   172  	}
   173  	return ret
   174  }
   175  
   176  // GetMlogRegistryActive returns copy of all active components mapping
   177  func GetMLogRegistryActive() map[mlogComponent]*Logger {
   178  	mlogRegLock.RLock()
   179  	defer mlogRegLock.RUnlock()
   180  
   181  	ret := make(map[mlogComponent]*Logger)
   182  	for k, v := range mLogRegistryActive {
   183  		ret[k] = v
   184  	}
   185  	return ret
   186  }
   187  
   188  // MLogRegisterComponentsFromContext receives a comma-separated string of
   189  // desired mlog components.
   190  // It returns an error if the specified mlog component is unavailable.
   191  // For each available component, the desires mlog components are registered as active,
   192  // creating new loggers for each.
   193  // If the string begins with '!', the function will remove the following components from the
   194  // default list
   195  func MLogRegisterComponentsFromContext(s string) error {
   196  	// negation
   197  	var negation bool
   198  	if strings.HasPrefix(s, "!") {
   199  		negation = true
   200  		s = strings.TrimPrefix(s, "!")
   201  	}
   202  	ss := strings.Split(s, ",")
   203  
   204  	registry := GetMLogRegistryAvailable()
   205  
   206  	if !negation {
   207  		for _, c := range ss {
   208  			ct := strings.TrimSpace(c)
   209  			if _, ok := registry[mlogComponent(ct)]; !ok {
   210  				return fmt.Errorf("%v: '%s'", errMLogComponentUnavailable, ct)
   211  			}
   212  			MLogRegisterActive(mlogComponent(ct))
   213  		}
   214  		return nil
   215  	}
   216  	// Register all
   217  	for c := range registry {
   218  		MLogRegisterActive(c)
   219  	}
   220  	// then remove
   221  	for _, u := range ss {
   222  		ct := strings.TrimSpace(u)
   223  		mlogRegisterInactive(mlogComponent(ct))
   224  	}
   225  	return nil
   226  }
   227  
   228  // MLogRegisterActive registers a component for mlogging.
   229  // Only registered loggers will write to mlog file.
   230  func MLogRegisterActive(component mlogComponent) {
   231  	mlogRegLock.Lock()
   232  	mLogRegistryActive[component] = NewLogger(string(component))
   233  	mlogRegLock.Unlock()
   234  }
   235  
   236  func mlogRegisterInactive(component mlogComponent) {
   237  	mlogRegLock.Lock()
   238  	delete(mLogRegistryActive, component) // noop if nil
   239  	mlogRegLock.Unlock()
   240  }
   241  
   242  // SendMLog writes enabled component mlogs to file if the component is registered active.
   243  func (msg *MLogT) Send(c mlogComponent) {
   244  	mlogRegLock.RLock()
   245  	if l, exists := mLogRegistryActive[c]; exists {
   246  		l.SendFormatted(GetMLogFormat(), 1, msg, c)
   247  	}
   248  	mlogRegLock.RUnlock()
   249  }
   250  
   251  func (l *Logger) SendFormatted(format mlogFormatT, level LogLevel, msg *MLogT, c mlogComponent) {
   252  	switch format {
   253  	case mLOGKV:
   254  		l.Sendln(level, msg.FormatKV())
   255  	case MLOGJSON:
   256  		logMessageC <- stdMsg{level, string(msg.FormatJSON(c))}
   257  	case mLOGPlain:
   258  		l.Sendln(level, string(msg.FormatPlain()))
   259  	//case MLOGDocumentation:
   260  	// don't handle this because this is just for one-off help/usage printing documentation
   261  	default:
   262  		glog.Fatalf("Unknown mlog format: %v", format)
   263  	}
   264  }
   265  
   266  // SetMLogDir sets the mlog directory, into which one mlog file per session
   267  // will be written.
   268  func SetMLogDir(str string) {
   269  	*mLogDir = str
   270  }
   271  
   272  func GetMLogDir() string {
   273  	m := *mLogDir
   274  	return m
   275  }
   276  
   277  func SetMLogFormat(format mlogFormatT) {
   278  	mLogFormat = format
   279  }
   280  
   281  func GetMLogFormat() mlogFormatT {
   282  	return mLogFormat
   283  }
   284  
   285  func SetMLogFormatFromString(formatString string) error {
   286  	if f := MLogStringToFormat[formatString]; f < 1 {
   287  		return ErrUnkownMLogFormat
   288  	} else {
   289  		SetMLogFormat(f)
   290  	}
   291  	return nil
   292  }
   293  
   294  func createLogDirs() error {
   295  	if *mLogDir != "" {
   296  		return os.MkdirAll(*mLogDir, os.ModePerm)
   297  	}
   298  	return errors.New("createLogDirs received empty string")
   299  }
   300  
   301  // shortHostname returns its argument, truncating at the first period.
   302  // For instance, given "www.google.com" it returns "www".
   303  func shortHostname(hostname string) string {
   304  	if i := strings.Index(hostname, "."); i >= 0 {
   305  		return hostname[:i]
   306  	}
   307  	return hostname
   308  }
   309  
   310  // logName returns a new log file name containing tag, with start time t, and
   311  // the name for the symlink for tag.
   312  func logName(t time.Time) (name, link string) {
   313  	name = fmt.Sprintf("%s.mlog.%s.%04d%02d%02d-%02d%02d%02d.%d.log",
   314  		program,
   315  		common.SessionID,
   316  		t.Year(),
   317  		t.Month(),
   318  		t.Day(),
   319  		t.Hour(),
   320  		t.Minute(),
   321  		t.Second(),
   322  		pid)
   323  	return name, program + ".log"
   324  }
   325  
   326  // CreateMLogFile creates a new log file and returns the file and its filename, which
   327  // contains tag ("INFO", "FATAL", etc.) and t.  If the file is created
   328  // successfully, create also attempts to update the symlink for that tag, ignoring
   329  // errors.
   330  func CreateMLogFile(t time.Time) (f *os.File, filename string, err error) {
   331  
   332  	if e := createLogDirs(); e != nil {
   333  		return nil, "", e
   334  	}
   335  
   336  	name, link := logName(t)
   337  	fname := filepath.Join(*mLogDir, name)
   338  
   339  	f, e := os.Create(fname)
   340  	if e != nil {
   341  		err = e
   342  		return nil, fname, err
   343  	}
   344  
   345  	symlink := filepath.Join(*mLogDir, link)
   346  	os.Remove(symlink)        // ignore err
   347  	os.Symlink(name, symlink) // ignore err
   348  
   349  	return f, fname, nil
   350  }
   351  
   352  func (m *MLogT) placeholderize() {
   353  	placeholderEmpty := "-"
   354  	if m.Receiver == "" {
   355  		m.Receiver = placeholderEmpty
   356  	}
   357  	if m.Subject == "" {
   358  		m.Subject = placeholderEmpty
   359  	}
   360  	if m.Verb == "" {
   361  		m.Verb = placeholderEmpty
   362  	}
   363  }
   364  
   365  func (m *MLogT) FormatJSON(c mlogComponent) []byte {
   366  	b, _ := m.MarshalJSON(c)
   367  	return b
   368  }
   369  
   370  func (m *MLogT) FormatKV() (out string) {
   371  	m.Lock()
   372  	defer m.Unlock()
   373  	m.placeholderize()
   374  	out = fmt.Sprintf("%s %s %s session=%s", m.Receiver, m.Verb, m.Subject, common.SessionID)
   375  	for _, d := range m.Details {
   376  		v := fmt.Sprintf("%v", d.Value)
   377  		// quote strings which contains spaces
   378  		if strings.Contains(v, " ") {
   379  			v = fmt.Sprintf(`"%v"`, d.Value)
   380  		}
   381  		out += fmt.Sprintf(" %s=%v", d.EventName(), v)
   382  	}
   383  	return out
   384  }
   385  
   386  func (m *MLogT) FormatPlain() (out string) {
   387  	m.Lock()
   388  	defer m.Unlock()
   389  	m.placeholderize()
   390  	out = fmt.Sprintf("%s %s %s %s", m.Receiver, m.Verb, m.Subject, common.SessionID)
   391  	for _, d := range m.Details {
   392  		v := fmt.Sprintf("%v", d.Value)
   393  		// quote strings which contains spaces
   394  		if strings.Contains(v, " ") {
   395  			v = fmt.Sprintf(`"%v"`, d.Value)
   396  		}
   397  		out += fmt.Sprintf(" %v", v)
   398  	}
   399  	return out
   400  }
   401  
   402  func (m *MLogT) MarshalJSON(c mlogComponent) ([]byte, error) {
   403  	m.Lock()
   404  	defer m.Unlock()
   405  	var obj = make(map[string]interface{})
   406  	obj["event"] = m.EventName()
   407  	obj["ts"] = time.Now()
   408  	obj["session"] = common.SessionID
   409  	obj["component"] = string(c)
   410  	for _, d := range m.Details {
   411  		obj[d.EventName()] = d.Value
   412  	}
   413  	return json.Marshal(obj)
   414  }
   415  
   416  func (m *MLogT) FormatJSONExample(c mlogComponent) []byte {
   417  	mm := &MLogT{
   418  		Receiver: m.Receiver,
   419  		Verb:     m.Verb,
   420  		Subject:  m.Subject,
   421  	}
   422  	var dets []MLogDetailT
   423  	for _, d := range m.Details {
   424  		ex := mlogInterfaceExamples[d.Value.(string)]
   425  		// Type of var not matched to interface example
   426  		if ex == "" {
   427  			continue
   428  		}
   429  		dets = append(dets, MLogDetailT{
   430  			Owner: d.Owner,
   431  			Key:   d.Key,
   432  			Value: ex,
   433  		})
   434  	}
   435  	mm.Details = dets
   436  	b, _ := mm.MarshalJSON(c)
   437  	return b
   438  }
   439  
   440  // FormatDocumentation prints wiki-ready documentation for all available component mlog LINES.
   441  // Output should be in markdown.
   442  func (m *MLogT) FormatDocumentation(cmp mlogComponent) (out string) {
   443  
   444  	// Get the json example before converting to abstract literal format, eg STRING -> $STRING
   445  	// This keeps the interface example dictionary as a separate concern.
   446  	exJSON := string(m.FormatJSONExample(cmp))
   447  
   448  	// Set up arbitrary documentation abstract literal format
   449  	docDetails := []MLogDetailT{}
   450  	for _, d := range m.Details {
   451  		dd := d.AsDocumentation()
   452  		docDetails = append(docDetails, *dd)
   453  	}
   454  	m.Details = docDetails
   455  
   456  	exPlain := m.FormatPlain()
   457  	exKV := m.FormatKV()
   458  
   459  	t := time.Now()
   460  	lStandardHeaderDateTime := fmt.Sprintf("%4d/%02d/%02d %02d:%02d:%02d",
   461  		t.Year(), t.Month(), t.Day(),
   462  		t.Hour(), t.Minute(), t.Second())
   463  	cmpS := fmt.Sprintf("[%s]", cmp)
   464  
   465  	out += fmt.Sprintf(`
   466  #### %s %s %s
   467  %s
   468  
   469  __Key value__:
   470  `+"```"+`
   471  %s %s %s
   472  `+"```"+`
   473  
   474  __JSON__:
   475  `+"```json"+`
   476  %s
   477  `+"```"+`
   478  
   479  __Plain__:
   480  `+"```"+`
   481  %s %s %s
   482  `+"```"+`
   483  
   484  _%d detail values_:
   485  
   486  `, m.Receiver, m.Verb, m.Subject,
   487  		m.Description,
   488  		lStandardHeaderDateTime,
   489  		cmpS,
   490  		exKV,
   491  		exJSON,
   492  		lStandardHeaderDateTime,
   493  		cmpS,
   494  		exPlain,
   495  		len(m.Details))
   496  
   497  	var details string
   498  	for _, d := range m.Details {
   499  		details += fmt.Sprintf("- `%s`: %s\n", d.EventName(), d.Value)
   500  	}
   501  	details += "\n"
   502  
   503  	out += details
   504  	return out
   505  }
   506  
   507  // EventName implements the JsonMsg interface in case wanting to use existing half-established json logging system
   508  func (m *MLogT) EventName() string {
   509  	r := strings.ToLower(m.Receiver)
   510  	v := strings.ToLower(m.Verb)
   511  	s := strings.ToLower(m.Subject)
   512  	return strings.Join([]string{r, v, s}, ".")
   513  }
   514  
   515  func (m *MLogDetailT) EventName() string {
   516  	o := strings.ToLower(m.Owner)
   517  	k := strings.ToLower(m.Key)
   518  	return strings.Join([]string{o, k}, ".")
   519  }
   520  
   521  func (m *MLogDetailT) AsDocumentation() *MLogDetailT {
   522  	m.Value = fmt.Sprintf("$%s", m.Value)
   523  	return m
   524  }
   525  
   526  // AssignDetails is a setter function for setting values for pre-existing details.
   527  // It accepts a variadic number of empty interfaces.
   528  // If the number of arguments does not match  the number of established details
   529  // for the receiving MLogT, it will fatal error.
   530  // Arguments MUST be provided in the order in which they should be applied to the
   531  // slice of existing details.
   532  func (m *MLogT) AssignDetails(detailVals ...interface{}) *MLogT {
   533  	// Check for congruence between argument length and registered details.
   534  	if len(detailVals) != len(m.Details) {
   535  		glog.Fatal(m.EventName(), "wrong number of details set, want: ", len(m.Details), "got:", len(detailVals))
   536  	}
   537  
   538  	m.Lock()
   539  	for i, detailval := range detailVals {
   540  		m.Details[i].Value = detailval
   541  	}
   542  	m.Unlock()
   543  
   544  	return m
   545  }