...

Source file src/go.uber.org/zap/example_test.go

Documentation: go.uber.org/zap

     1  // Copyright (c) 2016 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package zap_test
    22  
    23  import (
    24  	"encoding/json"
    25  	"io"
    26  	"log"
    27  	"os"
    28  	"time"
    29  
    30  	"go.uber.org/zap"
    31  	"go.uber.org/zap/zapcore"
    32  )
    33  
    34  func Example_presets() {
    35  	// Using zap's preset constructors is the simplest way to get a feel for the
    36  	// package, but they don't allow much customization.
    37  	logger := zap.NewExample() // or NewProduction, or NewDevelopment
    38  	defer logger.Sync()
    39  
    40  	const url = "http://example.com"
    41  
    42  	// In most circumstances, use the SugaredLogger. It's 4-10x faster than most
    43  	// other structured logging packages and has a familiar, loosely-typed API.
    44  	sugar := logger.Sugar()
    45  	sugar.Infow("Failed to fetch URL.",
    46  		// Structured context as loosely typed key-value pairs.
    47  		"url", url,
    48  		"attempt", 3,
    49  		"backoff", time.Second,
    50  	)
    51  	sugar.Infof("Failed to fetch URL: %s", url)
    52  
    53  	// In the unusual situations where every microsecond matters, use the
    54  	// Logger. It's even faster than the SugaredLogger, but only supports
    55  	// structured logging.
    56  	logger.Info("Failed to fetch URL.",
    57  		// Structured context as strongly typed fields.
    58  		zap.String("url", url),
    59  		zap.Int("attempt", 3),
    60  		zap.Duration("backoff", time.Second),
    61  	)
    62  	// Output:
    63  	// {"level":"info","msg":"Failed to fetch URL.","url":"http://example.com","attempt":3,"backoff":"1s"}
    64  	// {"level":"info","msg":"Failed to fetch URL: http://example.com"}
    65  	// {"level":"info","msg":"Failed to fetch URL.","url":"http://example.com","attempt":3,"backoff":"1s"}
    66  }
    67  
    68  func Example_basicConfiguration() {
    69  	// For some users, the presets offered by the NewProduction, NewDevelopment,
    70  	// and NewExample constructors won't be appropriate. For most of those
    71  	// users, the bundled Config struct offers the right balance of flexibility
    72  	// and convenience. (For more complex needs, see the AdvancedConfiguration
    73  	// example.)
    74  	//
    75  	// See the documentation for Config and zapcore.EncoderConfig for all the
    76  	// available options.
    77  	rawJSON := []byte(`{
    78  	  "level": "debug",
    79  	  "encoding": "json",
    80  	  "outputPaths": ["stdout", "/tmp/logs"],
    81  	  "errorOutputPaths": ["stderr"],
    82  	  "initialFields": {"foo": "bar"},
    83  	  "encoderConfig": {
    84  	    "messageKey": "message",
    85  	    "levelKey": "level",
    86  	    "levelEncoder": "lowercase"
    87  	  }
    88  	}`)
    89  
    90  	var cfg zap.Config
    91  	if err := json.Unmarshal(rawJSON, &cfg); err != nil {
    92  		panic(err)
    93  	}
    94  	logger := zap.Must(cfg.Build())
    95  	defer logger.Sync()
    96  
    97  	logger.Info("logger construction succeeded")
    98  	// Output:
    99  	// {"level":"info","message":"logger construction succeeded","foo":"bar"}
   100  }
   101  
   102  func Example_advancedConfiguration() {
   103  	// The bundled Config struct only supports the most common configuration
   104  	// options. More complex needs, like splitting logs between multiple files
   105  	// or writing to non-file outputs, require use of the zapcore package.
   106  	//
   107  	// In this example, imagine we're both sending our logs to Kafka and writing
   108  	// them to the console. We'd like to encode the console output and the Kafka
   109  	// topics differently, and we'd also like special treatment for
   110  	// high-priority logs.
   111  
   112  	// First, define our level-handling logic.
   113  	highPriority := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
   114  		return lvl >= zapcore.ErrorLevel
   115  	})
   116  	lowPriority := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
   117  		return lvl < zapcore.ErrorLevel
   118  	})
   119  
   120  	// Assume that we have clients for two Kafka topics. The clients implement
   121  	// zapcore.WriteSyncer and are safe for concurrent use. (If they only
   122  	// implement io.Writer, we can use zapcore.AddSync to add a no-op Sync
   123  	// method. If they're not safe for concurrent use, we can add a protecting
   124  	// mutex with zapcore.Lock.)
   125  	topicDebugging := zapcore.AddSync(io.Discard)
   126  	topicErrors := zapcore.AddSync(io.Discard)
   127  
   128  	// High-priority output should also go to standard error, and low-priority
   129  	// output should also go to standard out.
   130  	consoleDebugging := zapcore.Lock(os.Stdout)
   131  	consoleErrors := zapcore.Lock(os.Stderr)
   132  
   133  	// Optimize the Kafka output for machine consumption and the console output
   134  	// for human operators.
   135  	kafkaEncoder := zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
   136  	consoleEncoder := zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig())
   137  
   138  	// Join the outputs, encoders, and level-handling functions into
   139  	// zapcore.Cores, then tee the four cores together.
   140  	core := zapcore.NewTee(
   141  		zapcore.NewCore(kafkaEncoder, topicErrors, highPriority),
   142  		zapcore.NewCore(consoleEncoder, consoleErrors, highPriority),
   143  		zapcore.NewCore(kafkaEncoder, topicDebugging, lowPriority),
   144  		zapcore.NewCore(consoleEncoder, consoleDebugging, lowPriority),
   145  	)
   146  
   147  	// From a zapcore.Core, it's easy to construct a Logger.
   148  	logger := zap.New(core)
   149  	defer logger.Sync()
   150  	logger.Info("constructed a logger")
   151  }
   152  
   153  func ExampleNamespace() {
   154  	logger := zap.NewExample()
   155  	defer logger.Sync()
   156  
   157  	logger.With(
   158  		zap.Namespace("metrics"),
   159  		zap.Int("counter", 1),
   160  	).Info("tracked some metrics")
   161  	// Output:
   162  	// {"level":"info","msg":"tracked some metrics","metrics":{"counter":1}}
   163  }
   164  
   165  type addr struct {
   166  	IP   string
   167  	Port int
   168  }
   169  
   170  type request struct {
   171  	URL    string
   172  	Listen addr
   173  	Remote addr
   174  }
   175  
   176  func (a addr) MarshalLogObject(enc zapcore.ObjectEncoder) error {
   177  	enc.AddString("ip", a.IP)
   178  	enc.AddInt("port", a.Port)
   179  	return nil
   180  }
   181  
   182  func (r *request) MarshalLogObject(enc zapcore.ObjectEncoder) error {
   183  	enc.AddString("url", r.URL)
   184  	zap.Inline(r.Listen).AddTo(enc)
   185  	return enc.AddObject("remote", r.Remote)
   186  }
   187  
   188  func ExampleObject() {
   189  	logger := zap.NewExample()
   190  	defer logger.Sync()
   191  
   192  	req := &request{
   193  		URL:    "/test",
   194  		Listen: addr{"127.0.0.1", 8080},
   195  		Remote: addr{"127.0.0.1", 31200},
   196  	}
   197  	logger.Info("new request, in nested object", zap.Object("req", req))
   198  	logger.Info("new request, inline", zap.Inline(req))
   199  	// Output:
   200  	// {"level":"info","msg":"new request, in nested object","req":{"url":"/test","ip":"127.0.0.1","port":8080,"remote":{"ip":"127.0.0.1","port":31200}}}
   201  	// {"level":"info","msg":"new request, inline","url":"/test","ip":"127.0.0.1","port":8080,"remote":{"ip":"127.0.0.1","port":31200}}
   202  }
   203  
   204  func ExampleNewStdLog() {
   205  	logger := zap.NewExample()
   206  	defer logger.Sync()
   207  
   208  	std := zap.NewStdLog(logger)
   209  	std.Print("standard logger wrapper")
   210  	// Output:
   211  	// {"level":"info","msg":"standard logger wrapper"}
   212  }
   213  
   214  func ExampleRedirectStdLog() {
   215  	logger := zap.NewExample()
   216  	defer logger.Sync()
   217  
   218  	undo := zap.RedirectStdLog(logger)
   219  	defer undo()
   220  
   221  	log.Print("redirected standard library")
   222  	// Output:
   223  	// {"level":"info","msg":"redirected standard library"}
   224  }
   225  
   226  func ExampleReplaceGlobals() {
   227  	logger := zap.NewExample()
   228  	defer logger.Sync()
   229  
   230  	undo := zap.ReplaceGlobals(logger)
   231  	defer undo()
   232  
   233  	zap.L().Info("replaced zap's global loggers")
   234  	// Output:
   235  	// {"level":"info","msg":"replaced zap's global loggers"}
   236  }
   237  
   238  func ExampleAtomicLevel() {
   239  	atom := zap.NewAtomicLevel()
   240  
   241  	// To keep the example deterministic, disable timestamps in the output.
   242  	encoderCfg := zap.NewProductionEncoderConfig()
   243  	encoderCfg.TimeKey = ""
   244  
   245  	logger := zap.New(zapcore.NewCore(
   246  		zapcore.NewJSONEncoder(encoderCfg),
   247  		zapcore.Lock(os.Stdout),
   248  		atom,
   249  	))
   250  	defer logger.Sync()
   251  
   252  	logger.Info("info logging enabled")
   253  
   254  	atom.SetLevel(zap.ErrorLevel)
   255  	logger.Info("info logging disabled")
   256  	// Output:
   257  	// {"level":"info","msg":"info logging enabled"}
   258  }
   259  
   260  func ExampleAtomicLevel_config() {
   261  	// The zap.Config struct includes an AtomicLevel. To use it, keep a
   262  	// reference to the Config.
   263  	rawJSON := []byte(`{
   264  		"level": "info",
   265  		"outputPaths": ["stdout"],
   266  		"errorOutputPaths": ["stderr"],
   267  		"encoding": "json",
   268  		"encoderConfig": {
   269  			"messageKey": "message",
   270  			"levelKey": "level",
   271  			"levelEncoder": "lowercase"
   272  		}
   273  	}`)
   274  	var cfg zap.Config
   275  	if err := json.Unmarshal(rawJSON, &cfg); err != nil {
   276  		panic(err)
   277  	}
   278  	logger := zap.Must(cfg.Build())
   279  	defer logger.Sync()
   280  
   281  	logger.Info("info logging enabled")
   282  
   283  	cfg.Level.SetLevel(zap.ErrorLevel)
   284  	logger.Info("info logging disabled")
   285  	// Output:
   286  	// {"level":"info","message":"info logging enabled"}
   287  }
   288  
   289  func ExampleLogger_Check() {
   290  	logger := zap.NewExample()
   291  	defer logger.Sync()
   292  
   293  	if ce := logger.Check(zap.DebugLevel, "debugging"); ce != nil {
   294  		// If debug-level log output isn't enabled or if zap's sampling would have
   295  		// dropped this log entry, we don't allocate the slice that holds these
   296  		// fields.
   297  		ce.Write(
   298  			zap.String("foo", "bar"),
   299  			zap.String("baz", "quux"),
   300  		)
   301  	}
   302  
   303  	// Output:
   304  	// {"level":"debug","msg":"debugging","foo":"bar","baz":"quux"}
   305  }
   306  
   307  func ExampleLogger_Named() {
   308  	logger := zap.NewExample()
   309  	defer logger.Sync()
   310  
   311  	// By default, Loggers are unnamed.
   312  	logger.Info("no name")
   313  
   314  	// The first call to Named sets the Logger name.
   315  	main := logger.Named("main")
   316  	main.Info("main logger")
   317  
   318  	// Additional calls to Named create a period-separated path.
   319  	main.Named("subpackage").Info("sub-logger")
   320  	// Output:
   321  	// {"level":"info","msg":"no name"}
   322  	// {"level":"info","logger":"main","msg":"main logger"}
   323  	// {"level":"info","logger":"main.subpackage","msg":"sub-logger"}
   324  }
   325  
   326  func ExampleWrapCore_replace() {
   327  	// Replacing a Logger's core can alter fundamental behaviors.
   328  	// For example, it can convert a Logger to a no-op.
   329  	nop := zap.WrapCore(func(zapcore.Core) zapcore.Core {
   330  		return zapcore.NewNopCore()
   331  	})
   332  
   333  	logger := zap.NewExample()
   334  	defer logger.Sync()
   335  
   336  	logger.Info("working")
   337  	logger.WithOptions(nop).Info("no-op")
   338  	logger.Info("original logger still works")
   339  	// Output:
   340  	// {"level":"info","msg":"working"}
   341  	// {"level":"info","msg":"original logger still works"}
   342  }
   343  
   344  func ExampleWrapCore_wrap() {
   345  	// Wrapping a Logger's core can extend its functionality. As a trivial
   346  	// example, it can double-write all logs.
   347  	doubled := zap.WrapCore(func(c zapcore.Core) zapcore.Core {
   348  		return zapcore.NewTee(c, c)
   349  	})
   350  
   351  	logger := zap.NewExample()
   352  	defer logger.Sync()
   353  
   354  	logger.Info("single")
   355  	logger.WithOptions(doubled).Info("doubled")
   356  	// Output:
   357  	// {"level":"info","msg":"single"}
   358  	// {"level":"info","msg":"doubled"}
   359  	// {"level":"info","msg":"doubled"}
   360  }
   361  
   362  func ExampleDict() {
   363  	logger := zap.NewExample()
   364  	defer logger.Sync()
   365  
   366  	logger.Info("login event",
   367  		zap.Dict("event",
   368  			zap.Int("id", 123),
   369  			zap.String("name", "jane"),
   370  			zap.String("status", "pending")))
   371  	// Output:
   372  	// {"level":"info","msg":"login event","event":{"id":123,"name":"jane","status":"pending"}}
   373  }
   374  
   375  func ExampleObjects() {
   376  	logger := zap.NewExample()
   377  	defer logger.Sync()
   378  
   379  	// Use the Objects field constructor when you have a list of objects,
   380  	// all of which implement zapcore.ObjectMarshaler.
   381  	logger.Debug("opening connections",
   382  		zap.Objects("addrs", []addr{
   383  			{IP: "123.45.67.89", Port: 4040},
   384  			{IP: "127.0.0.1", Port: 4041},
   385  			{IP: "192.168.0.1", Port: 4042},
   386  		}))
   387  	// Output:
   388  	// {"level":"debug","msg":"opening connections","addrs":[{"ip":"123.45.67.89","port":4040},{"ip":"127.0.0.1","port":4041},{"ip":"192.168.0.1","port":4042}]}
   389  }
   390  
   391  func ExampleObjectValues() {
   392  	logger := zap.NewExample()
   393  	defer logger.Sync()
   394  
   395  	// Use the ObjectValues field constructor when you have a list of
   396  	// objects that do not implement zapcore.ObjectMarshaler directly,
   397  	// but on their pointer receivers.
   398  	logger.Debug("starting tunnels",
   399  		zap.ObjectValues("addrs", []request{
   400  			{
   401  				URL:    "/foo",
   402  				Listen: addr{"127.0.0.1", 8080},
   403  				Remote: addr{"123.45.67.89", 4040},
   404  			},
   405  			{
   406  				URL:    "/bar",
   407  				Listen: addr{"127.0.0.1", 8080},
   408  				Remote: addr{"127.0.0.1", 31200},
   409  			},
   410  		}))
   411  	// Output:
   412  	// {"level":"debug","msg":"starting tunnels","addrs":[{"url":"/foo","ip":"127.0.0.1","port":8080,"remote":{"ip":"123.45.67.89","port":4040}},{"url":"/bar","ip":"127.0.0.1","port":8080,"remote":{"ip":"127.0.0.1","port":31200}}]}
   413  }
   414  

View as plain text