TBR: cosnicolaou/llog: update to latest version and github account
- this has moved from cosmosnicolaou to cosnicolaou on github.com
- update to latest version
Rereading the rules on third_party I see that third party approver
approval is only needed for initial checkins. This is just a move
and update, so I'm just going to TBR this.
Change-Id: Ie038c9770190367cba6d254b84c2dee4e2257b98
diff --git a/go/src/github.com/cosnicolaou/llog/BUILD b/go/src/github.com/cosnicolaou/llog/BUILD
new file mode 100644
index 0000000..fbba143
--- /dev/null
+++ b/go/src/github.com/cosnicolaou/llog/BUILD
@@ -0,0 +1,10 @@
+# Copyright 2013 Google Inc. All Rights Reserved.
+#
+# Description:
+# Low-level Go support for leveled logs.
+
+licenses(["notice"]) # Apache 2.0
+
+exports_files(["LICENSE"])
+
+package(default_visibility = ["//visibility:public"])
diff --git a/go/src/github.com/cosnicolaou/llog/LICENSE b/go/src/github.com/cosnicolaou/llog/LICENSE
new file mode 100644
index 0000000..37ec93a
--- /dev/null
+++ b/go/src/github.com/cosnicolaou/llog/LICENSE
@@ -0,0 +1,191 @@
+Apache License
+Version 2.0, January 2004
+http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+"License" shall mean the terms and conditions for use, reproduction, and
+distribution as defined by Sections 1 through 9 of this document.
+
+"Licensor" shall mean the copyright owner or entity authorized by the copyright
+owner that is granting the License.
+
+"Legal Entity" shall mean the union of the acting entity and all other entities
+that control, are controlled by, or are under common control with that entity.
+For the purposes of this definition, "control" means (i) the power, direct or
+indirect, to cause the direction or management of such entity, whether by
+contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
+outstanding shares, or (iii) beneficial ownership of such entity.
+
+"You" (or "Your") shall mean an individual or Legal Entity exercising
+permissions granted by this License.
+
+"Source" form shall mean the preferred form for making modifications, including
+but not limited to software source code, documentation source, and configuration
+files.
+
+"Object" form shall mean any form resulting from mechanical transformation or
+translation of a Source form, including but not limited to compiled object code,
+generated documentation, and conversions to other media types.
+
+"Work" shall mean the work of authorship, whether in Source or Object form, made
+available under the License, as indicated by a copyright notice that is included
+in or attached to the work (an example is provided in the Appendix below).
+
+"Derivative Works" shall mean any work, whether in Source or Object form, that
+is based on (or derived from) the Work and for which the editorial revisions,
+annotations, elaborations, or other modifications represent, as a whole, an
+original work of authorship. For the purposes of this License, Derivative Works
+shall not include works that remain separable from, or merely link (or bind by
+name) to the interfaces of, the Work and Derivative Works thereof.
+
+"Contribution" shall mean any work of authorship, including the original version
+of the Work and any modifications or additions to that Work or Derivative Works
+thereof, that is intentionally submitted to Licensor for inclusion in the Work
+by the copyright owner or by an individual or Legal Entity authorized to submit
+on behalf of the copyright owner. For the purposes of this definition,
+"submitted" means any form of electronic, verbal, or written communication sent
+to the Licensor or its representatives, including but not limited to
+communication on electronic mailing lists, source code control systems, and
+issue tracking systems that are managed by, or on behalf of, the Licensor for
+the purpose of discussing and improving the Work, but excluding communication
+that is conspicuously marked or otherwise designated in writing by the copyright
+owner as "Not a Contribution."
+
+"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
+of whom a Contribution has been received by Licensor and subsequently
+incorporated within the Work.
+
+2. Grant of Copyright License.
+
+Subject to the terms and conditions of this License, each Contributor hereby
+grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
+irrevocable copyright license to reproduce, prepare Derivative Works of,
+publicly display, publicly perform, sublicense, and distribute the Work and such
+Derivative Works in Source or Object form.
+
+3. Grant of Patent License.
+
+Subject to the terms and conditions of this License, each Contributor hereby
+grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
+irrevocable (except as stated in this section) patent license to make, have
+made, use, offer to sell, sell, import, and otherwise transfer the Work, where
+such license applies only to those patent claims licensable by such Contributor
+that are necessarily infringed by their Contribution(s) alone or by combination
+of their Contribution(s) with the Work to which such Contribution(s) was
+submitted. If You institute patent litigation against any entity (including a
+cross-claim or counterclaim in a lawsuit) alleging that the Work or a
+Contribution incorporated within the Work constitutes direct or contributory
+patent infringement, then any patent licenses granted to You under this License
+for that Work shall terminate as of the date such litigation is filed.
+
+4. Redistribution.
+
+You may reproduce and distribute copies of the Work or Derivative Works thereof
+in any medium, with or without modifications, and in Source or Object form,
+provided that You meet the following conditions:
+
+You must give any other recipients of the Work or Derivative Works a copy of
+this License; and
+You must cause any modified files to carry prominent notices stating that You
+changed the files; and
+You must retain, in the Source form of any Derivative Works that You distribute,
+all copyright, patent, trademark, and attribution notices from the Source form
+of the Work, excluding those notices that do not pertain to any part of the
+Derivative Works; and
+If the Work includes a "NOTICE" text file as part of its distribution, then any
+Derivative Works that You distribute must include a readable copy of the
+attribution notices contained within such NOTICE file, excluding those notices
+that do not pertain to any part of the Derivative Works, in at least one of the
+following places: within a NOTICE text file distributed as part of the
+Derivative Works; within the Source form or documentation, if provided along
+with the Derivative Works; or, within a display generated by the Derivative
+Works, if and wherever such third-party notices normally appear. The contents of
+the NOTICE file are for informational purposes only and do not modify the
+License. You may add Your own attribution notices within Derivative Works that
+You distribute, alongside or as an addendum to the NOTICE text from the Work,
+provided that such additional attribution notices cannot be construed as
+modifying the License.
+You may add Your own copyright statement to Your modifications and may provide
+additional or different license terms and conditions for use, reproduction, or
+distribution of Your modifications, or for any such Derivative Works as a whole,
+provided Your use, reproduction, and distribution of the Work otherwise complies
+with the conditions stated in this License.
+
+5. Submission of Contributions.
+
+Unless You explicitly state otherwise, any Contribution intentionally submitted
+for inclusion in the Work by You to the Licensor shall be under the terms and
+conditions of this License, without any additional terms or conditions.
+Notwithstanding the above, nothing herein shall supersede or modify the terms of
+any separate license agreement you may have executed with Licensor regarding
+such Contributions.
+
+6. Trademarks.
+
+This License does not grant permission to use the trade names, trademarks,
+service marks, or product names of the Licensor, except as required for
+reasonable and customary use in describing the origin of the Work and
+reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty.
+
+Unless required by applicable law or agreed to in writing, Licensor provides the
+Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
+including, without limitation, any warranties or conditions of TITLE,
+NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
+solely responsible for determining the appropriateness of using or
+redistributing the Work and assume any risks associated with Your exercise of
+permissions under this License.
+
+8. Limitation of Liability.
+
+In no event and under no legal theory, whether in tort (including negligence),
+contract, or otherwise, unless required by applicable law (such as deliberate
+and grossly negligent acts) or agreed to in writing, shall any Contributor be
+liable to You for damages, including any direct, indirect, special, incidental,
+or consequential damages of any character arising as a result of this License or
+out of the use or inability to use the Work (including but not limited to
+damages for loss of goodwill, work stoppage, computer failure or malfunction, or
+any and all other commercial damages or losses), even if such Contributor has
+been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability.
+
+While redistributing the Work or Derivative Works thereof, You may choose to
+offer, and charge a fee for, acceptance of support, warranty, indemnity, or
+other liability obligations and/or rights consistent with this License. However,
+in accepting such obligations, You may act only on Your own behalf and on Your
+sole responsibility, not on behalf of any other Contributor, and only if You
+agree to indemnify, defend, and hold each Contributor harmless for any liability
+incurred by, or claims asserted against, such Contributor by reason of your
+accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work
+
+To apply the Apache License to your work, attach the following boilerplate
+notice, with the fields enclosed by brackets "[]" replaced with your own
+identifying information. (Don't include the brackets!) The text should be
+enclosed in the appropriate comment syntax for the file format. We also
+recommend that a file or class name and description of purpose be included on
+the same "printed page" as the copyright notice for easier identification within
+third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed 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.
diff --git a/go/src/github.com/cosnicolaou/llog/OWNERS b/go/src/github.com/cosnicolaou/llog/OWNERS
new file mode 100644
index 0000000..d9a7a2a
--- /dev/null
+++ b/go/src/github.com/cosnicolaou/llog/OWNERS
@@ -0,0 +1,2 @@
+p
+cnicolaou
diff --git a/go/src/github.com/cosnicolaou/llog/README b/go/src/github.com/cosnicolaou/llog/README
new file mode 100644
index 0000000..2dd74cc
--- /dev/null
+++ b/go/src/github.com/cosnicolaou/llog/README
@@ -0,0 +1,15 @@
+llog
+====
+
+Low level implementation of leveled execution logs for Go.
+
+It is a fork of the github.com/golang/glog (c6f9652c7179652e2fd8ed7002330db089f4c9db) package that is itself an efficient pure Go implementation of leveled
+logs in the manner of the open source C++ package
+
+ http://code.google.com/p/google-glog
+
+This version refactors the code so as not to rely on a single global variable
+and associated log state. It also separates out the flag parsing so that
+an application can chose to use whatever command line flags they wish to
+configure the package.
+
diff --git a/go/src/github.com/cosnicolaou/llog/README.google b/go/src/github.com/cosnicolaou/llog/README.google
new file mode 100644
index 0000000..d55ffce
--- /dev/null
+++ b/go/src/github.com/cosnicolaou/llog/README.google
@@ -0,0 +1,21 @@
+URL:https://github.com/cosnicolaou/llog/commit/ac9bbf37c10d39744f58b8f39205970557a64b10
+Version: ac9bbf37c10d39744f58b8f39205970557a64b10
+License: Apache 2.0
+License File: LICENSE
+
+Description:
+ Low level implementation of leveled execution logs for Go.
+
+It is a fork of the github.com/golang/glog package that is itself an efficient
+pure Go implementation of leveled logs in the manner of the open source C++
+package
+ http://code.google.com/p/google-glog
+
+This version refactors the code so as not to rely on a single global variable
+and associated log state. It also separates out the flag parsing so that
+an application can chose to use whatever command line flags they wish to
+configure the package.
+
+Local Modifications:
+None
+
diff --git a/go/src/github.com/cosnicolaou/llog/glog.go b/go/src/github.com/cosnicolaou/llog/glog.go
new file mode 100644
index 0000000..b39d25c
--- /dev/null
+++ b/go/src/github.com/cosnicolaou/llog/glog.go
@@ -0,0 +1,1139 @@
+// Low-level Go support for leveled logs, analogous to https://code.google.com/p/google-glog/, that avoids the use of global state and command line flags.
+//
+//
+// Copyright 2013 Google Inc. All Rights Reserved.
+//
+// Licensed 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 llog implements support for logging analogous to the
+// Google-internal C++ INFO/ERROR/V setup.
+// It provides a low-level class (llog.T) that encapsulates all state and
+// and methods of multi-leveled logging (i.e. Info, Warning, Error and Fatal)
+// and formatting variants such as Infof. Each instance of T may be named
+// and hence multiple sets of logs can be produced by a single process.
+// It also provides V-style logging controlled by the -v and -vmodule=file=2
+// style flags. Although command line flags are not directly implemented,
+// implementations of the Go 1.2 flags.Value interface are provided to
+// make it easy for users to parse their command line flags correctly.
+// The NewLogger factory function accepts positional parameters that correspond
+// google command line flags.
+//
+//
+// l := NewLogger("system")
+// l.Print(infoLog, Info("Prepare to repel boarders")
+//
+// l.Printf(fatalLog, "Initialization failed: %s", err)
+//
+// See the documentation for the V function for an explanation of these examples:
+//
+// if l.V(2) {
+// l.Print(infoLog,"Starting transaction...")
+// }
+//
+//
+// Log output is buffered and written periodically using Flush. Programs
+// should call Flush before exiting to guarantee all log output is written.
+//
+// By default, all log statements write to files in a temporary directory.
+// This package provides several methods that modify this behavior.
+// These methods must be called before any logging is done.
+//
+// NewLogger(name)
+//
+// SetLogDir(logDir)
+// log files will be written to this directory instead of the
+// default temporary directory.
+// SetLogToStderr(bool)
+// If true, logs are written to standard error instead of to files.
+// SetAlsoLogToStderr(bool)
+// If true, logs are written to standard error as well as to files.
+// SetStderrThreshold(level)
+// Log events at or above this severity are logged to standard
+// error as well as to files.
+// SetMaxStackBufSize(size)
+// Set the max size (bytes) of the byte buffer to use for stack
+// traces. The default max is 4096K; use powers of 2 since the
+// stack size will be grown exponentially until it exceeds the max.
+// A min of 128K is enforced and any attempts to reduce this will
+// be silently ignored.
+//
+// Other controls provide aids to debugging.
+//
+// SetLogBacktraceAt(location)
+// When set to a file and line number holding a logging statement,
+// such as
+// gopherflakes.go:234
+// a stack trace will be written to the Info log whenever execution
+// hits that statement. (Unlike with -vmodule, the ".go" must be
+// present.)
+// SetV(level)
+// Enable V-leveled logging at the specified level.
+// SetVModule(module)
+// The syntax of the argument is a comma-separated list of pattern=N,
+// where pattern is a literal file name (minus the ".go" suffix) or
+// "glob" pattern and N is a V level. For instance,
+// -gopher*=3
+// sets the V level to 3 in all Go files whose names begin "gopher".
+// SetVFilepath(regexp)
+// The syntax of the argument is as per VModule, expect that regular
+// expressions on the entire file path path are used instead of glob
+// patterns on the file name component as SetVModule.
+//
+package llog
+
+import (
+ "bufio"
+ "bytes"
+ "errors"
+ "fmt"
+ "io"
+ "os"
+ "path/filepath"
+ "regexp"
+ "runtime"
+ "strconv"
+ "strings"
+ "sync"
+ "sync/atomic"
+ "time"
+)
+
+// severity identifies the sort of log: info, warning etc. It also implements
+// the flag.Value interface. The -stderrthreshold flag is of type severity and
+// should be modified only through the flag.Value interface. The values match
+// the corresponding constants in C++.
+type Severity int32 // sync/atomic int32
+
+const (
+ InfoLog Severity = iota
+ WarningLog
+ ErrorLog
+ FatalLog
+ numSeverity = 4
+
+ initialMaxStackBufSize = 128 * 1024
+)
+
+const severityChar = "IWEF"
+
+var severityName = []string{
+ InfoLog: "INFO",
+ WarningLog: "WARNING",
+ ErrorLog: "ERROR",
+ FatalLog: "FATAL",
+}
+
+// get returns the value of the severity.
+func (s *Severity) get() Severity {
+ return Severity(atomic.LoadInt32((*int32)(s)))
+}
+
+// set sets the value of the severity.
+func (s *Severity) set(val Severity) {
+ atomic.StoreInt32((*int32)(s), int32(val))
+}
+
+// String is part of the flag.Value interface.
+func (s *Severity) String() string {
+ return strconv.FormatInt(int64(*s), 10)
+}
+
+// Set is part of the flag.Value interface.
+func (s *Severity) Set(value string) error {
+ var threshold Severity
+ // Is it a known name?
+ if v, ok := severityByName(value); ok {
+ threshold = v
+ } else {
+ v, err := strconv.Atoi(value)
+ if err != nil {
+ return err
+ }
+ threshold = Severity(v)
+ }
+ *s = threshold
+ return nil
+}
+
+func severityByName(s string) (Severity, bool) {
+ s = strings.ToUpper(s)
+ for i, name := range severityName {
+ if name == s {
+ return Severity(i), true
+ }
+ }
+ return 0, false
+}
+
+// OutputStats tracks the number of output lines and bytes written.
+type OutputStats struct {
+ lines int64
+ bytes int64
+}
+
+// Lines returns the number of lines written.
+func (s *OutputStats) Lines() int64 {
+ return atomic.LoadInt64(&s.lines)
+}
+
+// Bytes returns the number of bytes written.
+func (s *OutputStats) Bytes() int64 {
+ return atomic.LoadInt64(&s.bytes)
+}
+
+// Stats tracks the number of lines of output and number of bytes
+// per severity level.
+type Stats struct {
+ Info, Warning, Error OutputStats
+}
+
+// Level is exported because it appears in the arguments to V and is
+// the type of the v flag, which can be set programmatically.
+// It's a distinct type because we want to discriminate it from logType.
+// Variables of type level are only changed under logging.mu.
+// The -v flag is read only with atomic ops, so the state of the logging
+// module is consistent.
+
+// Level is treated as a sync/atomic int32.
+
+// Level specifies a level of verbosity for V logs. *Level implements
+// flag.Value; the -v flag is of type Level and should be modified
+// only through the flag.Value interface.
+type Level int32
+
+// get returns the value of the Level.
+func (l *Level) get() Level {
+ return Level(atomic.LoadInt32((*int32)(l)))
+}
+
+// set sets the value of the Level.
+func (l *Level) set(val Level) {
+ atomic.StoreInt32((*int32)(l), int32(val))
+}
+
+// String is part of the flag.Value interface.
+func (l *Level) String() string {
+ return strconv.FormatInt(int64(*l), 10)
+}
+
+// Get is part of the flag.Value interface.
+func (l *Level) Get() interface{} {
+ return *l
+}
+
+// Set is part of the flag.Value interface.
+func (l *Level) Set(value string) error {
+ v, err := strconv.Atoi(value)
+ if err != nil {
+ return err
+ }
+ *l = Level(v)
+ return nil
+}
+
+// moduleSpec represents the setting of the -vmodule flag.
+type ModuleSpec struct {
+ filter []modulePat
+}
+
+// modulePat contains a filter for the -vmodule flag.
+// It holds a verbosity level and a file pattern to match.
+type modulePat struct {
+ pattern string
+ literal bool // The pattern is a literal string
+ level Level
+}
+
+// match reports whether the file matches the pattern. It uses a string
+// comparison if the pattern contains no metacharacters.
+func (m *modulePat) match(file string) bool {
+ if m.literal {
+ return file == m.pattern
+ }
+ match, _ := filepath.Match(m.pattern, file)
+ return match
+}
+
+// FilepathSpec represents the setting of the -vfilepath flag.
+type FilepathSpec struct {
+ filter []filepathPat
+}
+
+// filepathPat contains a filter for the -vfilepath flags.
+type filepathPat struct {
+ regexp *regexp.Regexp
+ pattern string
+ level Level
+}
+
+// match reports whether the file path matches the regexp.
+func (f *filepathPat) match(path string) bool {
+ return f.regexp.MatchString(path)
+}
+
+func parseFilter(pat string) (string, int, error) {
+ if len(pat) == 0 {
+ // Empty strings such as from a trailing comma can be ignored.
+ return "", 0, nil
+ }
+ patLev := strings.Split(pat, "=")
+ if len(patLev) != 2 || len(patLev[0]) == 0 || len(patLev[1]) == 0 {
+ return "", 0, errVmoduleSyntax
+ }
+ pattern := patLev[0]
+ v, err := strconv.Atoi(patLev[1])
+ if err != nil {
+ return "", 0, errors.New("syntax error: expect comma-separated list of filename=N")
+ }
+ if v < 0 {
+ return "", 0, errors.New("negative value for vmodule level")
+ }
+ if v == 0 {
+ // Ignore. It's harmless but no point in paying the overhead.
+ return "", 0, nil
+ }
+ return pattern, v, nil
+}
+
+func (m *ModuleSpec) String() string {
+ var b bytes.Buffer
+ for i, f := range m.filter {
+ if i > 0 {
+ b.WriteRune(',')
+ }
+ fmt.Fprintf(&b, "%s=%d", f.pattern, f.level)
+ }
+ return b.String()
+}
+
+// Get is part of the (Go 1.2) flag.Getter interface. It always returns nil for this flag type since the
+// struct is not exported.
+func (m *ModuleSpec) Get() interface{} {
+ return nil
+}
+
+var errVmoduleSyntax = errors.New("syntax error: expect comma-separated list of filename=N")
+
+// Syntax: recordio=2,file=1,gfs*=3
+func (m *ModuleSpec) Set(value string) error {
+ var filter []modulePat
+ for _, pat := range strings.Split(value, ",") {
+ pattern, v, err := parseFilter(pat)
+ if err != nil {
+ return err
+ }
+ if v == 0 {
+ continue
+ }
+ // TODO: check syntax of filter?
+ filter = append(filter, modulePat{pattern, isLiteralGlob(pattern), Level(v)})
+ }
+ m.filter = filter
+ return nil
+}
+
+func (fp *FilepathSpec) String() string {
+ var b bytes.Buffer
+ for i, f := range fp.filter {
+ if i > 0 {
+ b.WriteRune(',')
+ }
+ fmt.Fprintf(&b, "%s=%d", f.pattern, f.level)
+ }
+ return b.String()
+}
+
+// Get is part of the (Go 1.2) flag.Getter interface. It always returns nil for this flag type since the
+// struct is not exported.
+func (p *FilepathSpec) Get() interface{} {
+ return nil
+}
+
+var errVpackageSyntax = errors.New("syntax error: expect comma-separated list of regexp=N")
+
+// Syntax: foo/bar=2,foo/bar/.*=1,f*=3
+func (p *FilepathSpec) Set(value string) error {
+ var filter []filepathPat
+ for _, pat := range strings.Split(value, ",") {
+ pattern, v, err := parseFilter(pat)
+ if err != nil {
+ return err
+ }
+ if v == 0 {
+ continue
+ }
+ re, err := regexp.Compile(pattern)
+ if err != nil {
+ return fmt.Errorf("failed to compile %s as an regexp: %s", pattern, err)
+ }
+ // TODO: check syntax of filter?
+ filter = append(filter, filepathPat{re, pattern, Level(v)})
+ }
+ p.filter = filter
+ return nil
+}
+
+// isLiteralGlob reports whether the pattern is a literal string, that is,
+// has no metacharacters that require filepath.Match to be called to match
+// the pattern.
+func isLiteralGlob(pattern string) bool {
+ return !strings.ContainsAny(pattern, `*?[]\`)
+}
+
+// traceLocation represents the setting of the -log_backtrace_at flag.
+type TraceLocation struct {
+ file string
+ line int
+}
+
+// isSet reports whether the trace location has been specified.
+// logging.mu is held.
+func (t *TraceLocation) isSet() bool {
+ return t.line > 0
+}
+
+// match reports whether the specified file and line matches the trace location.
+// The argument file name is the full path, not the basename specified in the flag.
+func (t *TraceLocation) match(file string, line int) bool {
+ if t.line != line {
+ return false
+ }
+ if i := strings.LastIndex(file, "/"); i >= 0 {
+ file = file[i+1:]
+ }
+ return t.file == file
+}
+
+func (t *TraceLocation) String() string {
+ // Lock because the type is not atomic. TODO: clean this up.
+ return fmt.Sprintf("%s:%d", t.file, t.line)
+}
+
+var errTraceSyntax = errors.New("syntax error: expect file.go:234")
+
+// Syntax: gopherflakes.go:234
+// Note that unlike vmodule the file extension is included here.
+func (t *TraceLocation) Set(value string) error {
+ if value == "" {
+ // Unset.
+ t.line = 0
+ t.file = ""
+ }
+ fields := strings.Split(value, ":")
+ if len(fields) != 2 {
+ return errTraceSyntax
+ }
+ file, line := fields[0], fields[1]
+ if !strings.Contains(file, ".") {
+ return errTraceSyntax
+ }
+ v, err := strconv.Atoi(line)
+ if err != nil {
+ return errTraceSyntax
+ }
+ if v <= 0 {
+ return errors.New("negative or zero value for level")
+ }
+ t.line = v
+ t.file = file
+ return nil
+}
+
+// flushSyncWriter is the interface satisfied by logging destinations.
+type flushSyncWriter interface {
+ Flush() error
+ Sync() error
+ io.Writer
+}
+
+// Log collects all the global state of the logging setup.
+type Log struct {
+ // the name of this logger (appears in the name of each log file)
+ name string
+
+ // logDirs lists the candidate directories for new log files.
+ logDirs []string
+
+ // Boolean flags. Not handled atomically because the flag.Value interface
+ // does not let us avoid the =true, and that shorthand is necessary for
+ // compatibility. TODO: does this matter enough to fix? Seems unlikely.
+ toStderr bool // The -logtostderr flag.
+ alsoToStderr bool // The -alsologtostderr flag.
+
+ // Level flag. Handled atomically.
+ stderrThreshold Severity // The -stderrthreshold flag.
+
+ // freeList is a list of byte buffers, maintained under freeListMu.
+ freeList *buffer
+
+ // freeListMu maintains the free list. It is separate from the main mutex
+ // so buffers can be grabbed and printed to without holding the main lock,
+ // for better parallelization.
+ freeListMu sync.Mutex
+
+ // mu protects the remaining elements of this structure and is
+ // used to synchronize logging.
+ mu sync.Mutex
+
+ // file holds writer for each of the log types.
+ file [numSeverity]flushSyncWriter
+
+ // pcs is used in V to avoid an allocation when computing the caller's PC.
+ pcs [1]uintptr
+
+ // vmap is a cache of the V Level for each V() call site, identified by PC.
+ // It is wiped whenever the vmodule flag changes state.
+ vmap map[uintptr]Level
+
+ // filterLength stores the length of the vmodule filter chain. If greater
+ // than zero, it means vmodule is enabled. It may be read safely
+ // using sync.LoadInt32, but is only modified under mu.
+ filterLength int32
+
+ // traceLocation is the state of the -log_backtrace_at flag.
+ traceLocation TraceLocation
+ // These flags are modified only under lock, although verbosity may be fetched
+ // safely using atomic.LoadInt32.
+ vmodule ModuleSpec // The state of the -vmodule flag.
+ vfilepath FilepathSpec // The state of the -vfilepath flag.
+ verbosity Level // V logging level, the value of the -v flag/
+
+ // track lines/bytes per severity level
+ stats Stats
+ severityStats [numSeverity]*OutputStats
+
+ // number of stack frame to skip in order to reach the callpoint
+ // to be logged. skip is calculated as per runtime.Caller.
+ skip int
+
+ // max size of buffer to use for stacks.
+ maxStackBufSize int
+}
+
+// NewLogger creates a new logger.
+// name is a non-empty string that appears in the names of log files
+// to distinguish between separate instances of the logger writing to the
+// same directory.
+// skip is the number of stack frames to skip in order to reach the
+// call point to be logged. 0 will log the caller of the logging methods,
+// 1 their caller etc.
+func NewLogger(name string, skip int) *Log {
+ logging := &Log{}
+ logging.setVState(0, nil, nil, false)
+ logging.skip = 2 + skip
+ logging.maxStackBufSize = 4096 * 1024
+ logging.name = name
+
+ // Default stderrThreshold is ERROR.
+ logging.stderrThreshold = ErrorLog
+ logging.setVState(0, nil, nil, false)
+
+ logging.severityStats[InfoLog] = &logging.stats.Info
+ logging.severityStats[WarningLog] = &logging.stats.Warning
+ logging.severityStats[ErrorLog] = &logging.stats.Error
+
+ logging.logDirs = append(logging.logDirs, os.TempDir())
+ go logging.flushDaemon()
+ return logging
+}
+
+func (l *Log) String() string {
+ return fmt.Sprintf("name=%s logdirs=%s logtostderr=%t alsologtostderr=%t max_stack_buf_size=%d v=%d stderrthreshold=%s vmodule=%s log_backtrace_at=%s",
+ l.name, l.logDirs, l.toStderr, l.alsoToStderr, l.maxStackBufSize, l.verbosity, &l.stderrThreshold, &l.vmodule, &l.traceLocation)
+}
+
+// logDir if non-empty, write log files to this directory.
+func (l *Log) SetLogDir(logDir string) {
+ if logDir != "" {
+ l.mu.Lock()
+ defer l.mu.Unlock()
+ l.logDirs = append([]string{logDir}, l.logDirs...)
+ }
+}
+
+// SetLogToStderr sets the flag that, if true, logs to standard error instead of files
+func (l *Log) SetLogToStderr(f bool) {
+ l.mu.Lock()
+ defer l.mu.Unlock()
+ l.toStderr = f
+
+}
+
+// SetAlsoLogToStderr sets the flag that, if true, logs to standard error as well as files
+func (l *Log) SetAlsoLogToStderr(f bool) {
+ l.mu.Lock()
+ defer l.mu.Unlock()
+ l.alsoToStderr = f
+
+}
+
+// SetV sets the log level for V logs
+func (l *Log) SetV(v Level) {
+ l.verbosity.set(v)
+}
+
+// SetStderrThreshold sets the threshold for which logs at or above which go to stderr
+func (l *Log) SetStderrThreshold(s Severity) {
+ l.stderrThreshold.set(s)
+}
+
+// SetModuleSpec sets the comma-separated list of pattern=N settings for
+// file-filtered logging
+func (l *Log) SetVModule(spec ModuleSpec) {
+ l.mu.Lock()
+ defer l.mu.Unlock()
+ l.setVState(l.verbosity, spec.filter, nil, true)
+}
+
+// SetModuleSpec sets the comma-separated list of pattern=N settings for
+// file-filtered logging
+func (l *Log) SetVFilepath(spec FilepathSpec) {
+ l.mu.Lock()
+ defer l.mu.Unlock()
+ l.setVState(l.verbosity, nil, spec.filter, true)
+}
+
+// SetTaceLocation sets the location, file:N, which when encountered will cause logging to emit a stack trace
+func (l *Log) SetTraceLocation(location TraceLocation) {
+ l.mu.Lock()
+ defer l.mu.Unlock()
+ l.traceLocation = location
+}
+
+func (l *Log) SetMaxStackBufSize(max int) {
+ l.mu.Lock()
+ defer l.mu.Unlock()
+ if max > initialMaxStackBufSize {
+ l.maxStackBufSize = max
+ }
+}
+
+// buffer holds a byte Buffer for reuse. The zero value is ready for use.
+type buffer struct {
+ bytes.Buffer
+ tmp [64]byte // temporary byte array for creating headers.
+ next *buffer
+}
+
+// setVState sets a consistent state for V logging. A nil value for
+// modules or filepaths will result in that filter not being changed.
+// l.mu is held.
+func (l *Log) setVState(verbosity Level, modules []modulePat, filepaths []filepathPat, setFilter bool) {
+ // Turn verbosity off so V will not fire while we are in transition.
+ l.verbosity.set(0)
+ // Ditto for filter length.
+ atomic.StoreInt32(&l.filterLength, 0)
+
+ // Set the new filters and wipe the pc->Level map if the filter has changed.
+ nfilters := 0
+ if setFilter {
+ if modules != nil {
+ l.vmodule.filter = modules
+ }
+ if filepaths != nil {
+ l.vfilepath.filter = filepaths
+ }
+ nfilters = len(l.vmodule.filter) + len(l.vfilepath.filter)
+ l.vmap = make(map[uintptr]Level)
+ }
+
+ // Things are consistent now, so enable filtering and verbosity.
+ // They are enabled in order opposite to that in V.
+ atomic.StoreInt32(&l.filterLength, int32(nfilters))
+ l.verbosity.set(verbosity)
+}
+
+// getBuffer returns a new, ready-to-use buffer.
+func (l *Log) getBuffer() *buffer {
+ l.freeListMu.Lock()
+ b := l.freeList
+ if b != nil {
+ l.freeList = b.next
+ }
+ l.freeListMu.Unlock()
+ if b == nil {
+ b = new(buffer)
+ } else {
+ b.next = nil
+ b.Reset()
+ }
+ return b
+}
+
+// putBuffer returns a buffer to the free list.
+func (l *Log) putBuffer(b *buffer) {
+ if b.Len() >= 256 {
+ // Let big buffers die a natural death.
+ return
+ }
+ l.freeListMu.Lock()
+ b.next = l.freeList
+ l.freeList = b
+ l.freeListMu.Unlock()
+}
+
+var timeNow = time.Now // Stubbed out for testing.
+
+/*
+header formats a log header as defined by the C++ implementation.
+It returns a buffer containing the formatted header.
+The depth specifies how many stack frames above lives the source line to be identified in the log message.
+
+Log lines have this form:
+ Lmmdd hh:mm:ss.uuuuuu threadid file:line] msg...
+where the fields are defined as follows:
+ L A single character, representing the log level (eg 'I' for INFO)
+ mm The month (zero padded; ie May is '05')
+ dd The day (zero padded)
+ hh:mm:ss.uuuuuu Time in hours, minutes and fractional seconds
+ threadid The space-padded thread ID as returned by GetTID()
+ file The file name
+ line The line number
+ msg The user-supplied message
+*/
+func (l *Log) header(s Severity, depth int) (*buffer, string, int) {
+ // Lmmdd hh:mm:ss.uuuuuu threadid file:line]
+ now := timeNow()
+ _, file, line, ok := runtime.Caller(l.skip + depth)
+ if !ok {
+ file = "???"
+ line = 1
+ } else {
+ slash := strings.LastIndex(file, "/")
+ if slash >= 0 {
+ file = file[slash+1:]
+ }
+ }
+ if line < 0 {
+ line = 0 // not a real line number, but acceptable to someDigits
+ }
+ if s > FatalLog {
+ s = InfoLog // for safety.
+ }
+ buf := l.getBuffer()
+
+ // Avoid Fprintf, for speed. The format is so simple that we can do it quickly by hand.
+ // It's worth about 3X. Fprintf is hard.
+ _, month, day := now.Date()
+ hour, minute, second := now.Clock()
+ buf.tmp[0] = severityChar[s]
+ buf.twoDigits(1, int(month))
+ buf.twoDigits(3, day)
+ buf.tmp[5] = ' '
+ buf.twoDigits(6, hour)
+ buf.tmp[8] = ':'
+ buf.twoDigits(9, minute)
+ buf.tmp[11] = ':'
+ buf.twoDigits(12, second)
+ buf.tmp[14] = '.'
+ buf.nDigits(6, 15, now.Nanosecond()/1000, '0')
+ buf.tmp[21] = ' '
+ buf.nDigits(7, 22, pid, ' ') // TODO: should be TID
+ buf.tmp[29] = ' '
+ buf.Write(buf.tmp[:30])
+ buf.WriteString(file)
+ buf.tmp[0] = ':'
+ n := buf.someDigits(1, line)
+ buf.tmp[n+1] = ']'
+ buf.tmp[n+2] = ' '
+ buf.Write(buf.tmp[:n+3])
+ return buf, file, line
+}
+
+// Some custom tiny helper functions to print the log header efficiently.
+
+const digits = "0123456789"
+
+// twoDigits formats a zero-prefixed two-digit integer at buf.tmp[i].
+func (buf *buffer) twoDigits(i, d int) {
+ buf.tmp[i+1] = digits[d%10]
+ d /= 10
+ buf.tmp[i] = digits[d%10]
+}
+
+// nDigits formats an n-digit integer at buf.tmp[i],
+// padding with pad on the left.
+// It assumes d >= 0.
+func (buf *buffer) nDigits(n, i, d int, pad byte) {
+ j := n - 1
+ for ; j >= 0 && d > 0; j-- {
+ buf.tmp[i+j] = digits[d%10]
+ d /= 10
+ }
+ for ; j >= 0; j-- {
+ buf.tmp[i+j] = pad
+ }
+}
+
+// someDigits formats a zero-prefixed variable-width integer at buf.tmp[i].
+func (buf *buffer) someDigits(i, d int) int {
+ // Print into the top, then copy down. We know there's space for at least
+ // a 10-digit number.
+ j := len(buf.tmp)
+ for {
+ j--
+ buf.tmp[j] = digits[d%10]
+ d /= 10
+ if d == 0 {
+ break
+ }
+ }
+ return copy(buf.tmp[i:], buf.tmp[j:])
+}
+
+func (l *Log) Println(s Severity, args ...interface{}) {
+ l.PrintlnDepth(s, 1, args...)
+}
+
+func (l *Log) Print(s Severity, args ...interface{}) {
+ l.PrintDepth(s, 1, args...)
+}
+
+func (l *Log) Printf(s Severity, format string, args ...interface{}) {
+ l.PrintfDepth(s, 1, format, args...)
+}
+
+func (l *Log) PrintlnDepth(s Severity, depth int, args ...interface{}) {
+ buf, file, line := l.header(s, depth)
+ fmt.Fprintln(buf, args...)
+ l.output(s, buf, file, line)
+}
+
+func (l *Log) PrintDepth(s Severity, depth int, args ...interface{}) {
+ buf, file, line := l.header(s, depth)
+ fmt.Fprint(buf, args...)
+ if buf.Bytes()[buf.Len()-1] != '\n' {
+ buf.WriteByte('\n')
+ }
+ l.output(s, buf, file, line)
+}
+
+func (l *Log) PrintfDepth(s Severity, depth int, format string, args ...interface{}) {
+ buf, file, line := l.header(s, depth)
+ fmt.Fprintf(buf, format, args...)
+ if buf.Bytes()[buf.Len()-1] != '\n' {
+ buf.WriteByte('\n')
+ }
+ l.output(s, buf, file, line)
+}
+
+// output writes the data to the log files and releases the buffer.
+func (l *Log) output(s Severity, buf *buffer, file string, line int) {
+ l.mu.Lock()
+ if l.traceLocation.isSet() {
+ if l.traceLocation.match(file, line) {
+ buf.Write(stacks(false, l.maxStackBufSize))
+ }
+ }
+ data := buf.Bytes()
+ if l.toStderr {
+ os.Stderr.Write(data)
+ } else {
+ if l.alsoToStderr || s >= l.stderrThreshold.get() {
+ os.Stderr.Write(data)
+ }
+ if l.file[s] == nil {
+ if err := l.createFiles(s); err != nil {
+ os.Stderr.Write(data) // Make sure the message appears somewhere.
+ l.exit(err)
+ }
+ }
+ switch s {
+ case FatalLog:
+ l.file[FatalLog].Write(data)
+ fallthrough
+ case ErrorLog:
+ l.file[ErrorLog].Write(data)
+ fallthrough
+ case WarningLog:
+ l.file[WarningLog].Write(data)
+ fallthrough
+ case InfoLog:
+ l.file[InfoLog].Write(data)
+ }
+ }
+ if s == FatalLog {
+ // Make sure we see the trace for the current goroutine on standard error.
+ if !l.toStderr {
+ os.Stderr.Write(stacks(false, l.maxStackBufSize))
+ }
+ // Write the stack trace for all goroutines to the files.
+ trace := stacks(true, l.maxStackBufSize)
+ logExitFunc = func(error) {} // If we get a write error, we'll still exit below.
+ for log := FatalLog; log >= InfoLog; log-- {
+ if f := l.file[log]; f != nil { // Can be nil if -logtostderr is set.
+ f.Write(trace)
+ }
+ }
+ l.mu.Unlock()
+ timeoutFlush(l, 10*time.Second)
+ os.Exit(255) // C++ uses -1, which is silly because it's anded with 255 anyway.
+ }
+ l.putBuffer(buf)
+ l.mu.Unlock()
+ if stats := l.severityStats[s]; stats != nil {
+ atomic.AddInt64(&stats.lines, 1)
+ atomic.AddInt64(&stats.bytes, int64(len(data)))
+ }
+}
+
+// timeoutFlush calls Flush and returns when it completes or after timeout
+// elapses, whichever happens first. This is needed because the hooks invoked
+// by Flush may deadlock when glog.Fatal is called from a hook that holds
+// a lock.
+func timeoutFlush(l *Log, timeout time.Duration) {
+ done := make(chan bool, 1)
+ go func() {
+ l.lockAndFlushAll()
+ done <- true
+ }()
+ select {
+ case <-done:
+ case <-time.After(timeout):
+ fmt.Fprintln(os.Stderr, "glog: Flush took longer than", timeout)
+ }
+}
+
+// stacks is a wrapper for runtime.Stack that attempts to recover the data for all goroutines.
+func stacks(all bool, max int) []byte {
+ // We don't know how big the traces are, so grow a few times if they don't fit. Start large, though.
+ n := initialMaxStackBufSize
+ var trace []byte
+ for n <= max {
+ trace = make([]byte, n)
+ nbytes := runtime.Stack(trace, all)
+ if nbytes < len(trace) {
+ return trace[:nbytes]
+ }
+ n *= 2
+ }
+ return trace
+}
+
+// logExitFunc provides a simple mechanism to override the default behavior
+// of exiting on error. Used in testing and to guarantee we reach a required exit
+// for fatal logs. Instead, exit could be a function rather than a method but that
+// would make its use clumsier.
+var logExitFunc func(error)
+
+// exit is called if there is trouble creating or writing log files.
+// It flushes the logs and exits the program; there's no point in hanging around.
+// l.mu is held.
+func (l *Log) exit(err error) {
+ fmt.Fprintf(os.Stderr, "log: exiting because of error: %s\n", err)
+ // If logExitFunc is set, we do that instead of exiting.
+ if logExitFunc != nil {
+ logExitFunc(err)
+ return
+ }
+ l.flushAll()
+ os.Exit(2)
+}
+
+// syncBuffer joins a bufio.Writer to its underlying file, providing access to the
+// file's Sync method and providing a wrapper for the Write method that provides log
+// file rotation. There are conflicting methods, so the file cannot be embedded.
+// l.mu is held for all its methods.
+type syncBuffer struct {
+ logger *Log
+ *bufio.Writer
+ file *os.File
+ sev Severity
+ nbytes uint64 // The number of bytes written to this file
+}
+
+func (sb *syncBuffer) Sync() error {
+ return sb.file.Sync()
+}
+
+func (sb *syncBuffer) Write(p []byte) (n int, err error) {
+ if sb.nbytes+uint64(len(p)) >= MaxSize {
+ if err := sb.rotateFile(time.Now()); err != nil {
+ sb.logger.exit(err)
+ }
+ }
+ n, err = sb.Writer.Write(p)
+ sb.nbytes += uint64(n)
+ if err != nil {
+ sb.logger.exit(err)
+ }
+ return
+}
+
+// rotateFile closes the syncBuffer's file and starts a new one.
+func (sb *syncBuffer) rotateFile(now time.Time) error {
+ if sb.file != nil {
+ sb.Flush()
+ sb.file.Close()
+ }
+ var err error
+ sb.file, _, err = sb.logger.create(severityName[sb.sev], now)
+ sb.nbytes = 0
+ if err != nil {
+ return err
+ }
+
+ sb.Writer = bufio.NewWriterSize(sb.file, bufferSize)
+
+ // Write header.
+ var buf bytes.Buffer
+ fmt.Fprintf(&buf, "Log file created at: %s\n", now.Format("2006/01/02 15:04:05"))
+ fmt.Fprintf(&buf, "Running on machine: %s\n", host)
+ fmt.Fprintf(&buf, "Binary: Built with %s %s for %s/%s\n", runtime.Compiler, runtime.Version(), runtime.GOOS, runtime.GOARCH)
+ fmt.Fprintf(&buf, "Log line format: [IWEF]mmdd hh:mm:ss.uuuuuu threadid file:line] msg\n")
+ n, err := sb.file.Write(buf.Bytes())
+ sb.nbytes += uint64(n)
+ return err
+}
+
+// bufferSize sizes the buffer associated with each log file. It's large
+// so that log records can accumulate without the logging thread blocking
+// on disk I/O. The flushDaemon will block instead.
+const bufferSize = 256 * 1024
+
+// createFiles creates all the log files for severity from sev down to infoLog.
+// l.mu is held.
+func (l *Log) createFiles(sev Severity) error {
+ now := time.Now()
+ // Files are created in decreasing severity order, so as soon as we find one
+ // has already been created, we can stop.
+ for s := sev; s >= InfoLog && l.file[s] == nil; s-- {
+ sb := &syncBuffer{
+ logger: l,
+ sev: s,
+ }
+ if err := sb.rotateFile(now); err != nil {
+ return err
+ }
+ l.file[s] = sb
+ }
+ return nil
+}
+
+const flushInterval = 30 * time.Second
+
+// flushDaemon periodically flushes the log file buffers.
+func (l *Log) flushDaemon() {
+ for _ = range time.NewTicker(flushInterval).C {
+ l.lockAndFlushAll()
+ }
+}
+
+// lockAndFlushAll is like flushAll but locks l.mu first.
+func (l *Log) lockAndFlushAll() {
+ l.mu.Lock()
+ l.flushAll()
+ l.mu.Unlock()
+}
+
+func (l *Log) Flush() {
+ l.lockAndFlushAll()
+}
+
+// flushAll flushes all the logs and attempts to "sync" their data to disk.
+// l.mu is held.
+func (l *Log) flushAll() {
+ // Flush from fatal down, in case there's trouble flushing.
+ for s := FatalLog; s >= InfoLog; s-- {
+ file := l.file[s]
+ if file != nil {
+ file.Flush() // ignore error
+ file.Sync() // ignore error
+ }
+ }
+}
+
+// setV computes and remembers the V level for a given PC
+// when vmodule is enabled.
+// File pattern matching takes the basename of the file, stripped
+// of its .go suffix, and uses 270.Match, which is a little more
+// general than the *? matching used in C++.
+// l.mu is held.
+func (l *Log) setV(pc uintptr) Level {
+ fn := runtime.FuncForPC(pc)
+ file, _ := fn.FileLine(pc)
+ // The file is something like /a/b/c/d.go. We want just the d.
+ if strings.HasSuffix(file, ".go") {
+ file = file[:len(file)-3]
+ }
+ module := file
+ if slash := strings.LastIndex(file, "/"); slash >= 0 {
+ module = file[slash+1:]
+ }
+ for _, filter := range l.vmodule.filter {
+ if filter.match(module) {
+ l.vmap[pc] = filter.level
+ return filter.level
+ }
+ }
+ for _, filter := range l.vfilepath.filter {
+ if filter.match(file) {
+ l.vmap[pc] = filter.level
+ return filter.level
+ }
+ }
+ l.vmap[pc] = 0
+ return 0
+}
+
+// V reports whether verbosity at the call site is at least the requested level.
+// The returned value is a boolean of type Verbose, which implements Info, Infoln
+// and Infof. These methods will write to the Info log if called.
+// Thus, one may write either
+// if glog.V(2) { glog.Info("log this") }
+// or
+// glog.V(2).Info("log this")
+// The second form is shorter but the first is cheaper if logging is off because it does
+// not evaluate its arguments.
+//
+// Whether an individual call to V generates a log record depends on the setting of
+// the -v and --vmodule flags; both are off by default. If the level in the call to
+// V is at least the value of -v, or of -vmodule for the source file containing the
+// call, the V call will log.
+func (l *Log) V(level Level) bool {
+ // This function tries hard to be cheap unless there's work to do.
+ // The fast path is two atomic loads and compares.
+
+ // Here is a cheap but safe test to see if V logging is enabled globally.
+ if l.verbosity.get() >= level {
+ return true
+ }
+
+ // It's off globally but it vmodule may still be set.
+ // Here is another cheap but safe test to see if vmodule is enabled.
+ if atomic.LoadInt32(&l.filterLength) > 0 {
+ // Now we need a proper lock to use the logging structure. The pcs field
+ // is shared so we must lock before accessing it. This is fairly expensive,
+ // but if V logging is enabled we're slow anyway.
+ l.mu.Lock()
+ defer l.mu.Unlock()
+ // Note that runtime.Callers counts skip differently to
+ // runtime.Caller - i.e. it is one greater than the skip
+ // value for .Caller to reach the same stack frame. So, even though
+ // we are one level closer to the caller here, we will still use the
+ // same value as for runtime.Caller!
+ if runtime.Callers(l.skip, l.pcs[:]) == 0 {
+ return false
+ }
+ v, ok := l.vmap[l.pcs[0]]
+ if !ok {
+ v = l.setV(l.pcs[0])
+ }
+ return v >= level
+ }
+ return false
+}
+
+func (l *Log) Stats() Stats {
+ return l.stats
+}
diff --git a/go/src/github.com/cosnicolaou/llog/glog_file.go b/go/src/github.com/cosnicolaou/llog/glog_file.go
new file mode 100644
index 0000000..dd0615a
--- /dev/null
+++ b/go/src/github.com/cosnicolaou/llog/glog_file.go
@@ -0,0 +1,106 @@
+// Low-level Go support for leveled logs, analogous to https://code.google.com/p/google-glog/, that avoids the use of global state and command line flags.
+//
+// Copyright 2013 Google Inc. All Rights Reserved.
+//
+// Licensed 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.
+
+// File I/O for logs.
+
+package llog
+
+import (
+ "errors"
+ "fmt"
+ "os"
+ "os/user"
+ "path/filepath"
+ "strings"
+ "time"
+)
+
+// MaxSize is the maximum size of a log file in bytes.
+var MaxSize uint64 = 1024 * 1024 * 1800
+
+var (
+ pid = os.Getpid()
+ program = filepath.Base(os.Args[0])
+ host = "unknownhost"
+ userName = "unknownuser"
+)
+
+func init() {
+ h, err := os.Hostname()
+ if err == nil {
+ host = shortHostname(h)
+ }
+
+ current, err := user.Current()
+ if err == nil {
+ userName = current.Username
+ }
+
+ // Sanitize userName since it may contain filepath separators on Windows.
+ userName = strings.Replace(userName, `\`, "_", -1)
+}
+
+// shortHostname returns its argument, truncating at the first period.
+// For instance, given "www.google.com" it returns "www".
+func shortHostname(hostname string) string {
+ if i := strings.Index(hostname, "."); i >= 0 {
+ return hostname[:i]
+ }
+ return hostname
+}
+
+// logName returns a new log file name containing tag, with start time t, and
+// the name for the symlink for tag.
+func logName(logger, tag string, t time.Time) (name, link string) {
+ name = fmt.Sprintf("%s.%s.%s.log.%s.%s.%04d%02d%02d-%02d%02d%02d.%d",
+ program,
+ host,
+ userName,
+ logger,
+ tag,
+ t.Year(),
+ t.Month(),
+ t.Day(),
+ t.Hour(),
+ t.Minute(),
+ t.Second(),
+ pid)
+ return name, program + "." + tag
+}
+
+// create creates a new log file and returns the file and its filename, which
+// contains tag ("INFO", "FATAL", etc.) and t. If the file is created
+// successfully, create also attempts to update the symlink for that tag, ignoring
+// errors.
+func (l *Log) create(tag string, t time.Time) (f *os.File, filename string, err error) {
+ if len(l.logDirs) == 0 {
+ return nil, "", errors.New("log: no log dirs")
+ }
+ name, link := logName(l.name, tag, t)
+ var lastErr error
+ for _, dir := range l.logDirs {
+ fname := filepath.Join(dir, name)
+ f, err := os.Create(fname)
+ if err == nil {
+ symlink := filepath.Join(dir, link)
+ os.Remove(symlink) // ignore err
+ os.Symlink(name, symlink) // ignore err
+ return f, fname, nil
+ }
+ lastErr = err
+ }
+ return nil, "", fmt.Errorf("log: cannot create log: %v", lastErr)
+}
diff --git a/go/src/github.com/cosnicolaou/llog/glog_test.go b/go/src/github.com/cosnicolaou/llog/glog_test.go
new file mode 100644
index 0000000..dafccc7
--- /dev/null
+++ b/go/src/github.com/cosnicolaou/llog/glog_test.go
@@ -0,0 +1,444 @@
+// Low-level go support for leveled logs, analogous to https://code.google.com/p/google-glog/, that avoids the use of global state and command line flags.
+//
+// Copyright 2013 Google Inc. All Rights Reserved.
+//
+// Licensed 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 llog
+
+import (
+ "bytes"
+ "fmt"
+ "path/filepath"
+ "runtime"
+ "strconv"
+ "strings"
+ "testing"
+ "time"
+)
+
+// Test that shortHostname works as advertised.
+func TestShortHostname(t *testing.T) {
+ for hostname, expect := range map[string]string{
+ "": "",
+ "host": "host",
+ "host.google.com": "host",
+ } {
+ if got := shortHostname(hostname); expect != got {
+ t.Errorf("shortHostname(%q): expected %q, got %q", hostname, expect, got)
+ }
+ }
+}
+
+// flushBuffer wraps a bytes.Buffer to satisfy flushSyncWriter.
+type flushBuffer struct {
+ bytes.Buffer
+}
+
+func (f *flushBuffer) Flush() error {
+ return nil
+}
+
+func (f *flushBuffer) Sync() error {
+ return nil
+}
+
+// swap sets the log writers and returns the old array.
+func (l *Log) swap(writers [numSeverity]flushSyncWriter) (old [numSeverity]flushSyncWriter) {
+ l.mu.Lock()
+ defer l.mu.Unlock()
+ old = l.file
+ for i, w := range writers {
+ l.file[i] = w
+ }
+ return
+}
+
+// newBuffers sets the log writers to all new byte buffers and returns the old array.
+func (l *Log) newBuffers() [numSeverity]flushSyncWriter {
+ return l.swap([numSeverity]flushSyncWriter{new(flushBuffer), new(flushBuffer), new(flushBuffer), new(flushBuffer)})
+}
+
+// contents returns the specified log value as a string.
+func (l *Log) contents(s Severity) string {
+ return l.file[s].(*flushBuffer).String()
+}
+
+// contains reports whether the string is contained in the log.
+func (l *Log) contains(s Severity, str string, t *testing.T) bool {
+ return strings.Contains(l.contents(s), str)
+}
+
+func newLogger(t *testing.T) *Log {
+ l := NewLogger("test", 0)
+ l.newBuffers()
+ return l
+}
+
+func TestFlags(t *testing.T) {
+ t.Skip()
+}
+
+func TestSwap(t *testing.T) {
+ t.Skip()
+}
+
+// Test that Info works as advertised.
+func TestInfo(t *testing.T) {
+ l := newLogger(t)
+ l.Print(InfoLog, "test")
+ if !l.contains(InfoLog, "I", t) {
+ t.Errorf("Info has wrong character: %q", l.contents(InfoLog))
+ }
+ if !l.contains(InfoLog, "test", t) {
+ t.Error("Info failed")
+ }
+}
+
+func TestInfoDepth(t *testing.T) {
+ l := newLogger(t)
+
+ f := func() { l.PrintDepth(InfoLog, 1, "depth-test1") }
+
+ // The next three lines must stay together
+ _, _, wantLine, _ := runtime.Caller(0)
+ l.PrintDepth(InfoLog, 0, "depth-test0")
+ f()
+
+ msgs := strings.Split(strings.TrimSuffix(l.contents(InfoLog), "\n"), "\n")
+ if len(msgs) != 2 {
+ t.Fatalf("Got %d lines, expected 2", len(msgs))
+ }
+
+ for i, m := range msgs {
+ if !strings.HasPrefix(m, "I") {
+ t.Errorf("InfoDepth[%d] has wrong character: %q", i, m)
+ }
+ w := fmt.Sprintf("depth-test%d", i)
+ if !strings.Contains(m, w) {
+ t.Errorf("InfoDepth[%d] missing %q: %q", i, w, m)
+ }
+
+ // pull out the line number (between : and ])
+ msg := m[strings.LastIndex(m, ":")+1:]
+ x := strings.Index(msg, "]")
+ if x < 0 {
+ t.Errorf("InfoDepth[%d]: missing ']': %q", i, m)
+ continue
+ }
+ line, err := strconv.Atoi(msg[:x])
+ if err != nil {
+ t.Errorf("InfoDepth[%d]: bad line number: %q", i, m)
+ continue
+ }
+ wantLine++
+ if wantLine != line {
+ t.Errorf("InfoDepth[%d]: got line %d, want %d", i, line, wantLine)
+ }
+ }
+}
+
+// Test that the header has the correct format.
+func TestHeader(t *testing.T) {
+ l := newLogger(t)
+ defer func(previous func() time.Time) { timeNow = previous }(timeNow)
+ timeNow = func() time.Time {
+ return time.Date(2006, 1, 2, 15, 4, 5, .067890e9, time.Local)
+ }
+ pid = 1234
+ l.Print(InfoLog, "test")
+ var line int
+ format := "I0102 15:04:05.067890 1234 glog_test.go:%d] test\n"
+ n, err := fmt.Sscanf(l.contents(InfoLog), format, &line)
+ if n != 1 || err != nil {
+ t.Errorf("log format error: %d elements, error %s:\n%s", n, err, l.contents(InfoLog))
+ }
+ // Scanf treats multiple spaces as equivalent to a single space,
+ // so check for correct space-padding also.
+ want := fmt.Sprintf(format, line)
+ if l.contents(InfoLog) != want {
+ t.Errorf("log format error: got:\n\t%q\nwant:\t%q", l.contents(InfoLog), want)
+ }
+}
+
+// Test that an Error log goes to Warning and Info.
+// Even in the Info log, the source character will be E, so the data should
+// all be identical.
+func TestError(t *testing.T) {
+ l := newLogger(t)
+ l.Print(ErrorLog, "test")
+ if !l.contains(ErrorLog, "E", t) {
+ t.Errorf("Error has wrong character: %q", l.contents(ErrorLog))
+ }
+ if !l.contains(ErrorLog, "test", t) {
+ t.Error("Error failed")
+ }
+ str := l.contents(ErrorLog)
+ if !l.contains(WarningLog, str, t) {
+ t.Error("Warning failed")
+ }
+ if !l.contains(InfoLog, str, t) {
+ t.Error("Info failed")
+ }
+}
+
+// Test that a Warning log goes to Info.
+// Even in the Info log, the source character will be W, so the data should
+// all be identical.
+func TestWarning(t *testing.T) {
+ l := newLogger(t)
+ l.Print(WarningLog, "test")
+ if !l.contains(WarningLog, "W", t) {
+ t.Errorf("Warning has wrong character: %q", l.contents(WarningLog))
+ }
+ if !l.contains(WarningLog, "test", t) {
+ t.Error("Warning failed")
+ }
+ str := l.contents(WarningLog)
+ if !l.contains(InfoLog, str, t) {
+ t.Error("Info failed")
+ }
+}
+
+// Test that a V log goes to Info.
+func TestV(t *testing.T) {
+ l := newLogger(t)
+ var v Level
+ v.Set("2")
+ l.SetV(v)
+ if l.V(2) {
+ l.Print(InfoLog, "test")
+ }
+ if !l.contains(InfoLog, "I", t) {
+ t.Errorf("Info has wrong character: %q", l.contents(InfoLog))
+ }
+ if !l.contains(InfoLog, "test", t) {
+ t.Error("Info failed")
+ }
+}
+
+// Test that a vmodule enables a log in this file.
+func TestVmoduleOn(t *testing.T) {
+ l := newLogger(t)
+ var spec ModuleSpec
+ spec.Set("glog_test=2")
+ l.SetVModule(spec)
+ if !l.V(1) {
+ t.Error("V not enabled for 1")
+ }
+ if !l.V(2) {
+ t.Error("V not enabled for 2")
+ }
+ if l.V(3) {
+ t.Error("V enabled for 3")
+ }
+ if l.V(2) {
+ l.Print(InfoLog, "test")
+ }
+ if !l.contains(InfoLog, "I", t) {
+ t.Errorf("Info has wrong character: %q", l.contents(InfoLog))
+ }
+ if !l.contains(InfoLog, "test", t) {
+ t.Error("Info failed")
+ }
+}
+
+// Test that a vmodule of another file does not enable a log in this file.
+func TestVmoduleOff(t *testing.T) {
+ l := newLogger(t)
+ var spec ModuleSpec
+ spec.Set("notthisfile=2")
+ l.SetVModule(spec)
+ for i := 1; i <= 3; i++ {
+ if l.V(Level(i)) {
+ t.Errorf("V enabled for %d", i)
+ }
+ }
+ if l.V(2) {
+ l.Print(InfoLog, "test")
+ }
+ if l.contents(InfoLog) != "" {
+ t.Error("V logged incorrectly")
+ }
+}
+
+// vGlobs are patterns that match/don't match this file at V=2.
+var vGlobs = map[string]bool{
+ // Easy to test the numeric match here.
+ "glog_test=1": false, // If -vmodule sets V to 1, V(2) will fail.
+ "glog_test=2": true,
+ "glog_test=3": true, // If -vmodule sets V to 1, V(3) will succeed.
+ // These all use 2 and check the patterns. All are true.
+ "*=2": true,
+ "?l*=2": true,
+ "????_*=2": true,
+ "??[mno]?_*t=2": true,
+ // These all use 2 and check the patterns. All are false.
+ "*x=2": false,
+ "m*=2": false,
+ "??_*=2": false,
+ "?[abc]?_*t=2": false,
+}
+
+// Test that vmodule globbing works as advertised.
+func testVmoduleGlob(pat string, match bool, t *testing.T) {
+ l := newLogger(t)
+ var spec ModuleSpec
+ spec.Set(pat)
+ l.SetVModule(spec)
+ if l.V(2) != match {
+ t.Errorf("incorrect match for %q: got %t expected %t", pat, l.V(2), match)
+ }
+}
+
+// Test that a vmodule globbing works as advertised.
+func TestVmoduleGlob(t *testing.T) {
+ for glob, match := range vGlobs {
+ testVmoduleGlob(glob, match, t)
+ }
+}
+
+// vRegexps are patterns that match/don't match this file at V=2.
+var vRegexps = map[string]bool{
+ // Easy to test the numeric match here.
+ "glog_test=1": false, // If -vfilepath sets V to 1, V(2) will fail.
+ "glog_test=2": true,
+ "glog_test=3": true, // If -vfilepath sets V to 1, V(3) will succeed.
+ // These all use 2 and check the patterns. All are true.
+ ".*=2": true,
+ ".l.*=2": true,
+ "...._.*=2": true,
+ "..[mno]?_.*t=2": true,
+ // These all use 2 and check the patterns. All are false.
+ ".*x=2": false,
+ "m.*=2": true,
+ ".._.*=2": true,
+ ".\\[abc\\]._.*=2": false,
+ "llog/.*=2": true,
+ "github.com/.*=2": true,
+}
+
+// Test that vfilepath regexps works as advertised.
+func testVfilepathRegexp(pat string, match bool, t *testing.T) {
+ l := newLogger(t)
+ var spec FilepathSpec
+ if err := spec.Set(pat); err != nil {
+ t.Fatal(err)
+ }
+ l.SetVFilepath(spec)
+ if l.V(2) != match {
+ t.Errorf("incorrect match for %q: got %t expected %t", pat, l.V(2), match)
+ }
+}
+
+// Test that a vfilepath regexps work as advertised.
+func TestVfilepathRegexp(t *testing.T) {
+ for re, match := range vRegexps {
+ testVfilepathRegexp(re, match, t)
+ }
+}
+
+func TestRollover(t *testing.T) {
+ l := newLogger(t)
+ l.swap([numSeverity]flushSyncWriter{nil, nil, nil, nil})
+ var err error
+ defer func(previous func(error)) { logExitFunc = previous }(logExitFunc)
+ logExitFunc = func(e error) {
+ err = e
+ }
+ defer func(previous uint64) { MaxSize = previous }(MaxSize)
+ MaxSize = 512
+
+ l.Print(InfoLog, "x") // Be sure we have a file.
+ info, ok := l.file[InfoLog].(*syncBuffer)
+ if !ok {
+ t.Fatal("info wasn't created")
+ }
+ if err != nil {
+ t.Fatalf("info has initial error: %v", err)
+ }
+ fname0 := info.file.Name()
+ l.Print(InfoLog, strings.Repeat("x", int(MaxSize))) // force a rollover
+ if err != nil {
+ t.Fatalf("info has error after big write: %v", err)
+ }
+
+ // Make sure the next log file gets a file name with a different
+ // time stamp.
+ //
+ // TODO: determine whether we need to support subsecond log
+ // rotation. C++ does not appear to handle this case (nor does it
+ // handle Daylight Savings Time properly).
+ time.Sleep(1 * time.Second)
+
+ l.Print(InfoLog, "x") // create a new file
+ if err != nil {
+ t.Fatalf("error after rotation: %v", err)
+ }
+ fname1 := info.file.Name()
+ if fname0 == fname1 {
+ t.Errorf("info.f.Name did not change: %v", fname0)
+ }
+ if info.nbytes >= MaxSize {
+ t.Errorf("file size was not reset: %d", info.nbytes)
+ }
+}
+
+func TestLogBacktraceAt(t *testing.T) {
+ l := newLogger(t)
+ // The peculiar style of this code simplifies line counting and maintenance of the
+ // tracing block below.
+ var infoLine string
+ setTraceLocation := func(file string, line int, ok bool, delta int) {
+ if !ok {
+ t.Fatal("could not get file:line")
+ }
+ _, file = filepath.Split(file)
+ infoLine = fmt.Sprintf("%s:%d", file, line+delta)
+ err := l.traceLocation.Set(infoLine)
+ if err != nil {
+ t.Fatal("error setting log_backtrace_at: ", err)
+ }
+ }
+ {
+ // Start of tracing block. These lines know about each other's relative position.
+ _, file, line, ok := runtime.Caller(0)
+ setTraceLocation(file, line, ok, +2) // Two lines between Caller and Info calls.
+ l.Print(InfoLog, "we want a stack trace here")
+ }
+ numAppearances := strings.Count(l.contents(InfoLog), infoLine)
+ if numAppearances < 2 {
+ // Need 2 appearances, one in the log header and one in the trace:
+ // log_test.go:281: I0511 16:36:06.952398 02238 log_test.go:280] we want a stack trace here
+ // ...
+ // github.com/glog/glog_test.go:280 (0x41ba91)
+ // ...
+ // We could be more precise but that would require knowing the details
+ // of the traceback format, which may not be dependable.
+ t.Fatal("got no trace back; log is ", l.contents(InfoLog))
+ }
+}
+
+func BenchmarkHeader(b *testing.B) {
+ l := newLogger(nil)
+ for i := 0; i < b.N; i++ {
+ buf, _, _ := l.header(InfoLog, 0)
+ l.putBuffer(buf)
+ }
+}
+
+func TestHeader1(t *testing.T) {
+ l := newLogger(t)
+ l.Print(InfoLog, "hello")
+ fmt.Println(l.contents(InfoLog))
+}