[
  {
    "path": ".gitignore",
    "content": "vendor/\n"
  },
  {
    "path": "LICENSE",
    "content": "ISC License\n\nCopyright (c) Blendle\n\nPermission to use, copy, modify, and/or distribute this software for any\npurpose with or without fee is hereby granted, provided that the above\ncopyright notice and this permission notice appear in all copies.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\nWITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\nMERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\nANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\nWHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\nACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\nOR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# :zap: Zapdriver\n\nBlazing fast, [Zap][zap]-based [Stackdriver][stackdriver] logging.\n\n[zap]: https://github.com/uber-go/zap\n[stackdriver]: https://cloud.google.com/stackdriver/\n\n## Usage\n\nThis package provides three building blocks to support the full array of\nstructured logging capabilities of Stackdriver:\n\n* [Special purpose logging fields](#special-purpose-logging-fields)\n* [Pre-configured Stackdriver-optimized encoder](#pre-configured-stackdriver-optimized-encoder)\n* [Custom Stackdriver Zap core](#custom-stackdriver-zap-core)\n* [Using Error Reporting](#using-error-reporting)\n\nThe above components can be used separately, but to start, you can create a new\nZap logger with all of the above included:\n\n```golang\nlogger, err := zapdriver.NewProduction() // with sampling\nlogger, err := zapdriver.NewDevelopment() // with `development` set to `true`\n```\n\nThe above functions give back a pointer to a `zap.Logger` object, so you can use\n[Zap][zap] like you've always done, except that it now logs in the proper\n[Stackdriver][stackdriver] format.\n\nYou can also create a configuration struct, and build your logger from there:\n\n```golang\nconfig := zapdriver.NewProductionConfig()\nconfig := zapdriver.NewDevelopmentConfig()\n```\n\nOr, get the Zapdriver encoder, and build your own configuration struct from\nthat:\n\n```golang\nencoder := zapdriver.NewProductionEncoderConfig()\nencoder := zapdriver.NewDevelopmentEncoderConfig()\n```\n\nRead on to learn more about the available Stackdriver-specific log fields, and\nhow to use the above-mentioned components.\n\n### Special purpose logging fields\n\nYou can use the following fields to add extra information to your log entries.\nThese fields are parsed by Stackdriver to make it easier to query your logs or\nto use the log details in the Stackdriver monitoring interface.\n\n* [`HTTP`](#http)\n* [`Label`](#label)\n* [`SourceLocation`](#sourcelocation)\n* [`Operation`](#operation)\n* [`TraceContext`](#tracecontext)\n\n#### HTTP\n\nYou can log HTTP request/response cycles using the following field:\n\n```golang\nHTTP(req *HTTPPayload) zap.Field\n```\n\nYou can either manually build the request payload:\n\n```golang\nreq := &HTTPPayload{\n  RequestMethod: \"GET\",\n  RequestURL: \"/\",\n  Status: 200,\n}\n```\n\nOr, you can auto generate the struct, based on the available request and\nresponse objects:\n\n```golang\nNewHTTP(req *http.Request, res *http.Response) *HTTPPayload\n```\n\nYou are free to pass in `nil` for either the request or response object, if one\nof them is unavailable to you at the point of logging. Any field depending on\none or the other will be omitted if `nil` is passed in.\n\nNote that there are some fields that are not populated by either the request or\nresponse object, and need to be set manually:\n\n* `ServerIP string`\n* `Latency string`\n* `CacheLookup bool`\n* `CacheHit bool`\n* `CacheValidatedWithOriginServer bool`\n* `CacheFillBytes string`\n\nIf you have no need for those fields, the quickest way to get started is like\nso:\n\n```golang\nlogger.Info(\"Request Received.\", zapdriver.HTTP(zapdriver.NewHTTP(req, res)))\n```\n\n#### Label\n\nYou can add a \"label\" to your payload as follows:\n\n```golang\nLabel(key, value string) zap.Field\n```\n\nNote that underwater, this sets the key to `labels.<key>`. You need to be using\nthe `zapdriver.Core` core for this to be converted to the proper format for\nStackdriver to recognize the labels.\n\nSee \"Custom Stackdriver Zap core\" for more details.\n\nIf you have a reason not to use the provided Core, you can still wrap labels in\nthe right `labels` namespace by using the available function:\n\n```golang\nLabels(fields ...zap.Field) zap.Field\n```\n\nLike so:\n\n```golang\nlogger.Info(\n  \"Did something.\",\n  zapdriver.Labels(\n    zapdriver.Label(\"hello\", \"world\"),\n    zapdriver.Label(\"hi\", \"universe\"),\n  ),\n)\n```\n\nAgain, wrapping the `Label` calls in `Labels` is not required if you use the\nsupplied Zap Core.\n\n#### SourceLocation\n\nYou can add a source code location to your log lines to be picked up by\nStackdriver.\n\nNote that you can set this manually, or use `zapdriver.Core` to automatically\nadd this. If you set it manually, _and_ use `zapdriver.Core`, the manual call\nstack will be preserved over the automated one.\n\n```golang\nSourceLocation(pc uintptr, file string, line int, ok bool) zap.Field\n```\n\nNote that the function signature equals that of the return values of\n`runtime.Caller()`. This allows you to catch the stack frame at one location,\nwhile logging it at a different location, like so:\n\n```golang\npc, file, line, ok := runtime.Caller(0)\n\n// do other stuff...\n\nlogger.Error(\"Something happened!\", zapdriver.SourceLocation(pc, file, line, ok))\n```\n\nIf you use `zapdriver.Core`, the above use-case is the only use-case where you\nwould want to manually set the source location. In all other situations, you can\nsimply omit this field, and it will be added automatically, using the stack\nframe at the location where the log line is triggered.\n\nIf you don't use `zapdriver.Core`, and still want to add the source location at\nthe frame of the triggered log line, you'd do it like this:\n\n```golang\nlogger.Error(\"Something happened!\", zapdriver.SourceLocation(runtime.Caller(0)))\n```\n\n#### Operation\n\nThe `Operation` log field allows you to group log lines into a single\n\"operation\" performed by the application:\n\n```golang\nOperation(id, producer string, first, last bool) zap.Field\n```\n\nFor a pair of logs that belong to the same operation, you should use the same\n`id` between them. The `producer` is an arbitrary identifier that should be\nglobally unique amongst all the logs of all your applications (meaning it should\nprobably be the unique name of the current application). You should set `first`\nto true for the first log in the operation, and `last` to true for the final log\nof the operation.\n\n```golang\nlogger.Info(\"Started.\", zapdriver.Operation(\"3g4d3g\", \"my-app\", true, false))\nlogger.Debug(\"Progressing.\", zapdriver.Operation(\"3g4d3g\", \"my-app\", false, false))\nlogger.Info(\"Done.\", zapdriver.Operation(\"3g4d3g\", \"my-app\", false, true))\n```\n\nInstead of defining the \"start\" and \"end\" booleans, you can also use these three\nconvenience functions:\n\n```golang\nOperationStart(id, producer string) zap.Field\nOperationCont(id, producer string) zap.Field\nOperationEnd(id, producer string) zap.Field\n```\n\n#### TraceContext\n\nYou can add trace context information to your log lines to be picked up by\nStackdriver.\n\n```golang\nTraceContext(trace string, spanId string, sampled bool, projectName string) []zap.Field\n```\n\nLike so:\n\n```golang\nlogger.Error(\"Something happened!\", zapdriver.TraceContext(\"105445aa7843bc8bf206b120001000\", \"0\", true, \"my-project-name\")...)\n```\n\n### Pre-configured Stackdriver-optimized encoder\n\nThe Stackdriver encoder maps all Zap log levels to the appropriate\n[Stackdriver-supported levels][levels]:\n\n> DEBUG     (100) Debug or trace information.\n>\n> INFO      (200) Routine information, such as ongoing status or performance.\n>\n> WARNING   (400) Warning events might cause problems.\n>\n> ERROR     (500) Error events are likely to cause problems.\n>\n> CRITICAL  (600) Critical events cause more severe problems or outages.\n>\n> ALERT     (700) A person must take an action immediately.\n>\n> EMERGENCY (800) One or more systems are unusable.\n\n[levels]: https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry#LogSeverity\n\nIt also sets some of the default keys to use [the right names][names], such as\n`timestamp`, `severity`, and `message`.\n\n[names]: https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry\n\nYou can use this encoder if you want to build your Zap logger configuration\nmanually:\n\n```golang\nzapdriver.NewProductionEncoderConfig()\n```\n\nFor parity-sake, there's also `zapdriver.NewDevelopmentEncoderConfig()`, but it\nreturns the exact same encoder right now.\n\n### Custom Stackdriver Zap core\n\nA custom Zap core is included in this package to support some special use-cases.\n\nFirst of all, if you use `zapdriver.NewProduction()` (or `NewDevelopment`) , you\nalready have this core enabled, so everything _just works_ ™.\n\nThere are two use-cases which require this core:\n\n1. If you use `zapdriver.Label(\"hello\", \"world\")`, it will initially end up in\n   your log with the key `labels.hello` and value `world`. Now if you have two\n   labels, you could also have `labels.hi` with value `universe`. This works as-\n   is, but for this to be correctly parsed by Stackdriver as true \"labels\", you\n   need to use the Zapdriver core, so that both of these fields get rewritten,\n   to use the namespace `labels`, and use the keys `hello` and `hi` within that\n   namespace. This is done automatically.\n\n2. If you don't want to use `zapdriver.SourceLocation()` on every log call, you\n   can use this core for the source location to be automatically added to\n   each log entry.\n\nWhen building a logger, you can inject the Zapdriver core as follows:\n\n```golang\nconfig := &zap.Config{}\nlogger, err := config.Build(zapdriver.WrapCore())\n```\n\n### Using Error Reporting\n\nTo report errors using StackDriver's Error Reporting tool, a log line needs to follow a separate log format described in the [Error Reporting][errorreporting] documentation.\n\n[errorreporting]: https://cloud.google.com/error-reporting/docs/formatting-error-messages\n\nThe simplest way to do this is by using `NewProductionWithCore`:\n\n```golang\nlogger, err := zapdriver.NewProductionWithCore(zapdriver.WrapCore(\n  zapdriver.ReportAllErrors(true),\n  zapdriver.ServiceName(\"my service\"),\n))\n```\n\nFor parity-sake, there's also `zapdriver.NewDevelopmentWithCore()`\n\nIf you are building a custom logger, you can use `WrapCore()` to configure the driver core:\n\n```golang\nconfig := &zap.Config{}\nlogger, err := config.Build(zapdriver.WrapCore(\n  zapdriver.ReportAllErrors(true),\n  zapdriver.ServiceName(\"my service\"),\n))\n```\n\nConfiguring this way, every error log entry will be reported to Stackdriver's Error Reporting tool.\n\n#### Reporting errors manually\n\nIf you do not want every error to be reported, you can attach `ErrorReport()` to log call manually:\n\n```golang\nlogger.Error(\"An error to be reported!\", zapdriver.ErrorReport(runtime.Caller(0)))\n// Or get Caller details\npc, file, line, ok := runtime.Caller(0)\n// do other stuff... and log elsewhere\nlogger.Error(\"Another error to be reported!\", zapdriver.ErrorReport(pc, file, line, ok))\n```\n\nPlease keep in mind that ErrorReport needs a ServiceContext attached to the log\nentry. If you did not configure this using `WrapCore`, error reports will\nget attached using service name as `unknown`. To prevent this from happeneing,\neither configure your core or attach service context before (or when) using\nthe logger:\n\n```golang\nlogger.Error(\n  \"An error to be reported!\",\n  zapdriver.ErrorReport(runtime.Caller(0)),\n  zapdriver.ServiceContext(\"my service\"),\n)\n\n// Or permanently attach it to your logger\nlogger = logger.With(zapdriver.ServiceContext(\"my service\"))\n// and then use it\nlogger.Error(\"An error to be reported!\", zapdriver.ErrorReport(runtime.Caller(0)))\n```\n"
  },
  {
    "path": "common_test.go",
    "content": "package zapdriver_test\n\nimport (\n\t\"time\"\n\n\t\"go.uber.org/zap/zapcore\"\n)\n\n// sliceArrayEncoder is an ArrayEncoder backed by a simple []interface{}. Like\n// the MapObjectEncoder, it's not designed for production use.\ntype sliceArrayEncoder struct {\n\telems []interface{}\n}\n\nfunc (s *sliceArrayEncoder) AppendArray(v zapcore.ArrayMarshaler) error {\n\tenc := &sliceArrayEncoder{}\n\terr := v.MarshalLogArray(enc)\n\ts.elems = append(s.elems, enc.elems)\n\treturn err\n}\n\nfunc (s *sliceArrayEncoder) AppendObject(v zapcore.ObjectMarshaler) error {\n\tm := zapcore.NewMapObjectEncoder()\n\terr := v.MarshalLogObject(m)\n\ts.elems = append(s.elems, m.Fields)\n\treturn err\n}\n\nfunc (s *sliceArrayEncoder) AppendReflected(v interface{}) error {\n\ts.elems = append(s.elems, v)\n\treturn nil\n}\n\nfunc (s *sliceArrayEncoder) AppendBool(v bool)              { s.elems = append(s.elems, v) }\nfunc (s *sliceArrayEncoder) AppendByteString(v []byte)      { s.elems = append(s.elems, v) }\nfunc (s *sliceArrayEncoder) AppendComplex128(v complex128)  { s.elems = append(s.elems, v) }\nfunc (s *sliceArrayEncoder) AppendComplex64(v complex64)    { s.elems = append(s.elems, v) }\nfunc (s *sliceArrayEncoder) AppendDuration(v time.Duration) { s.elems = append(s.elems, v) }\nfunc (s *sliceArrayEncoder) AppendFloat64(v float64)        { s.elems = append(s.elems, v) }\nfunc (s *sliceArrayEncoder) AppendFloat32(v float32)        { s.elems = append(s.elems, v) }\nfunc (s *sliceArrayEncoder) AppendInt(v int)                { s.elems = append(s.elems, v) }\nfunc (s *sliceArrayEncoder) AppendInt64(v int64)            { s.elems = append(s.elems, v) }\nfunc (s *sliceArrayEncoder) AppendInt32(v int32)            { s.elems = append(s.elems, v) }\nfunc (s *sliceArrayEncoder) AppendInt16(v int16)            { s.elems = append(s.elems, v) }\nfunc (s *sliceArrayEncoder) AppendInt8(v int8)              { s.elems = append(s.elems, v) }\nfunc (s *sliceArrayEncoder) AppendString(v string)          { s.elems = append(s.elems, v) }\nfunc (s *sliceArrayEncoder) AppendTime(v time.Time)         { s.elems = append(s.elems, v) }\nfunc (s *sliceArrayEncoder) AppendUint(v uint)              { s.elems = append(s.elems, v) }\nfunc (s *sliceArrayEncoder) AppendUint64(v uint64)          { s.elems = append(s.elems, v) }\nfunc (s *sliceArrayEncoder) AppendUint32(v uint32)          { s.elems = append(s.elems, v) }\nfunc (s *sliceArrayEncoder) AppendUint16(v uint16)          { s.elems = append(s.elems, v) }\nfunc (s *sliceArrayEncoder) AppendUint8(v uint8)            { s.elems = append(s.elems, v) }\nfunc (s *sliceArrayEncoder) AppendUintptr(v uintptr)        { s.elems = append(s.elems, v) }\n"
  },
  {
    "path": "config.go",
    "content": "package zapdriver\n\nimport (\n\t\"go.uber.org/zap\"\n\t\"go.uber.org/zap/zapcore\"\n)\n\n// NewProductionEncoderConfig returns an opinionated EncoderConfig for\n// production environments.\nfunc NewProductionEncoderConfig() zapcore.EncoderConfig {\n\treturn encoderConfig\n}\n\n// NewDevelopmentEncoderConfig returns an opinionated EncoderConfig for\n// development environments.\nfunc NewDevelopmentEncoderConfig() zapcore.EncoderConfig {\n\treturn encoderConfig\n}\n\n// NewProductionConfig is a reasonable production logging configuration.\n// Logging is enabled at InfoLevel and above.\n//\n// It uses a JSON encoder, writes to standard error, and enables sampling.\n// Stacktraces are automatically included on logs of ErrorLevel and above.\nfunc NewProductionConfig() zap.Config {\n\treturn zap.Config{\n\t\tLevel:       zap.NewAtomicLevelAt(zap.InfoLevel),\n\t\tDevelopment: false,\n\t\tSampling: &zap.SamplingConfig{\n\t\t\tInitial:    100,\n\t\t\tThereafter: 100,\n\t\t},\n\t\tEncoding:         \"json\",\n\t\tEncoderConfig:    NewProductionEncoderConfig(),\n\t\tOutputPaths:      []string{\"stderr\"},\n\t\tErrorOutputPaths: []string{\"stderr\"},\n\t}\n}\n\n// NewDevelopmentConfig is a reasonable development logging configuration.\n// Logging is enabled at DebugLevel and above.\n//\n// It enables development mode (which makes DPanicLevel logs panic), uses a\n// console encoder, writes to standard error, and disables sampling.\n// Stacktraces are automatically included on logs of WarnLevel and above.\nfunc NewDevelopmentConfig() zap.Config {\n\treturn zap.Config{\n\t\tLevel:            zap.NewAtomicLevelAt(zap.DebugLevel),\n\t\tDevelopment:      true,\n\t\tEncoding:         \"json\",\n\t\tEncoderConfig:    NewDevelopmentEncoderConfig(),\n\t\tOutputPaths:      []string{\"stderr\"},\n\t\tErrorOutputPaths: []string{\"stderr\"},\n\t}\n}\n"
  },
  {
    "path": "core.go",
    "content": "package zapdriver\n\nimport (\n\t\"strings\"\n\n\t\"go.uber.org/zap\"\n\t\"go.uber.org/zap/zapcore\"\n)\n\n// driverConfig is used to configure core.\ntype driverConfig struct {\n\t// Report all logs with level error or above to stackdriver using\n\t// `ErrorReport()` when set to true\n\tReportAllErrors bool\n\n\t// ServiceName is added as `ServiceContext()` to all logs when set\n\tServiceName string\n}\n\n// Core is a zapdriver specific core wrapped around the default zap core. It\n// allows to merge all defined labels\ntype core struct {\n\tzapcore.Core\n\n\t// permLabels is a collection of labels that have been added to the logger\n\t// through the use of `With()`. These labels should never be cleared after\n\t// logging a single entry, unlike `tempLabel`.\n\tpermLabels *labels\n\n\t// tempLabels keeps a record of all the labels that need to be applied to the\n\t// current log entry. Zap serializes log fields at different parts of the\n\t// stack, one such location is when calling `core.With` and the other one is\n\t// when calling `core.Write`. This makes it impossible to (for example) take\n\t// all `labels.xxx` fields, and wrap them in the `labels` namespace in one go.\n\t//\n\t// Instead, we have to filter out these labels at both locations, and then add\n\t// them back in the proper format right before we call `Write` on the original\n\t// Zap core.\n\ttempLabels *labels\n\n\t// Configuration for the zapdriver core\n\tconfig driverConfig\n}\n\n// zapdriver core option to report all logs with level error or above to stackdriver\n// using `ErrorReport()` when set to true\nfunc ReportAllErrors(report bool) func(*core) {\n\treturn func(c *core) {\n\t\tc.config.ReportAllErrors = report\n\t}\n}\n\n// zapdriver core option to add `ServiceContext()` to all logs with `name` as\n// service name\nfunc ServiceName(name string) func(*core) {\n\treturn func(c *core) {\n\t\tc.config.ServiceName = name\n\t}\n}\n\n// WrapCore returns a `zap.Option` that wraps the default core with the\n// zapdriver one.\nfunc WrapCore(options ...func(*core)) zap.Option {\n\treturn zap.WrapCore(func(c zapcore.Core) zapcore.Core {\n\t\tnewcore := &core{\n\t\t\tCore:       c,\n\t\t\tpermLabels: newLabels(),\n\t\t\ttempLabels: newLabels(),\n\t\t}\n\t\tfor _, option := range options {\n\t\t\toption(newcore)\n\t\t}\n\t\treturn newcore\n\t})\n}\n\n// With adds structured context to the Core.\nfunc (c *core) With(fields []zap.Field) zapcore.Core {\n\tvar lbls *labels\n\tlbls, fields = c.extractLabels(fields)\n\n\tlbls.mutex.RLock()\n\tc.permLabels.mutex.Lock()\n\tfor k, v := range lbls.store {\n\t\tc.permLabels.store[k] = v\n\t}\n\tc.permLabels.mutex.Unlock()\n\tlbls.mutex.RUnlock()\n\n\treturn &core{\n\t\tCore:       c.Core.With(fields),\n\t\tpermLabels: c.permLabels,\n\t\ttempLabels: newLabels(),\n\t\tconfig:     c.config,\n\t}\n}\n\n// Check determines whether the supplied Entry should be logged (using the\n// embedded LevelEnabler and possibly some extra logic). If the entry\n// should be logged, the Core adds itself to the CheckedEntry and returns\n// the result.\n//\n// Callers must use Check before calling Write.\nfunc (c *core) Check(ent zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry {\n\tif c.Enabled(ent.Level) {\n\t\treturn ce.AddCore(ent, c)\n\t}\n\n\treturn ce\n}\n\nfunc (c *core) Write(ent zapcore.Entry, fields []zapcore.Field) error {\n\tvar lbls *labels\n\tlbls, fields = c.extractLabels(fields)\n\n\tlbls.mutex.RLock()\n\tc.tempLabels.mutex.Lock()\n\tfor k, v := range lbls.store {\n\t\tc.tempLabels.store[k] = v\n\t}\n\tc.tempLabels.mutex.Unlock()\n\tlbls.mutex.RUnlock()\n\n\tfields = append(fields, labelsField(c.allLabels()))\n\tfields = c.withSourceLocation(ent, fields)\n\tif c.config.ServiceName != \"\" {\n\t\tfields = c.withServiceContext(c.config.ServiceName, fields)\n\t}\n\tif c.config.ReportAllErrors && zapcore.ErrorLevel.Enabled(ent.Level) {\n\t\tfields = c.withErrorReport(ent, fields)\n\t\tif c.config.ServiceName == \"\" {\n\t\t\t// A service name was not set but error report needs it\n\t\t\t// So attempt to add a generic service name\n\t\t\tfields = c.withServiceContext(\"unknown\", fields)\n\t\t}\n\t}\n\n\tc.tempLabels.reset()\n\n\treturn c.Core.Write(ent, fields)\n}\n\n// Sync flushes buffered logs (if any).\nfunc (c *core) Sync() error {\n\treturn c.Core.Sync()\n}\n\nfunc (c *core) allLabels() *labels {\n\tlbls := newLabels()\n\n\tlbls.mutex.Lock()\n\tc.permLabels.mutex.RLock()\n\tfor k, v := range c.permLabels.store {\n\t\tlbls.store[k] = v\n\t}\n\tc.permLabels.mutex.RUnlock()\n\n\tc.tempLabels.mutex.RLock()\n\tfor k, v := range c.tempLabels.store {\n\t\tlbls.store[k] = v\n\t}\n\tc.tempLabels.mutex.RUnlock()\n\tlbls.mutex.Unlock()\n\n\treturn lbls\n}\n\nfunc (c *core) extractLabels(fields []zapcore.Field) (*labels, []zapcore.Field) {\n\tlbls := newLabels()\n\tout := []zapcore.Field{}\n\n\tlbls.mutex.Lock()\n\tfor i := range fields {\n\t\tif !isLabelField(fields[i]) {\n\t\t\tout = append(out, fields[i])\n\t\t\tcontinue\n\t\t}\n\n\t\tlbls.store[strings.Replace(fields[i].Key, \"labels.\", \"\", 1)] = fields[i].String\n\t}\n\tlbls.mutex.Unlock()\n\n\treturn lbls, out\n}\n\nfunc (c *core) withLabels(fields []zapcore.Field) []zapcore.Field {\n\tlbls := newLabels()\n\tout := []zapcore.Field{}\n\n\tlbls.mutex.Lock()\n\tfor i := range fields {\n\t\tif isLabelField(fields[i]) {\n\t\t\tlbls.store[strings.Replace(fields[i].Key, \"labels.\", \"\", 1)] = fields[i].String\n\t\t\tcontinue\n\t\t}\n\n\t\tout = append(out, fields[i])\n\t}\n\tlbls.mutex.Unlock()\n\n\treturn append(out, labelsField(lbls))\n}\n\nfunc (c *core) withSourceLocation(ent zapcore.Entry, fields []zapcore.Field) []zapcore.Field {\n\t// If the source location was manually set, don't overwrite it\n\tfor i := range fields {\n\t\tif fields[i].Key == sourceKey {\n\t\t\treturn fields\n\t\t}\n\t}\n\n\tif !ent.Caller.Defined {\n\t\treturn fields\n\t}\n\n\treturn append(fields, SourceLocation(ent.Caller.PC, ent.Caller.File, ent.Caller.Line, true))\n}\n\nfunc (c *core) withServiceContext(name string, fields []zapcore.Field) []zapcore.Field {\n\t// If the service context was manually set, don't overwrite it\n\tfor i := range fields {\n\t\tif fields[i].Key == serviceContextKey {\n\t\t\treturn fields\n\t\t}\n\t}\n\n\treturn append(fields, ServiceContext(name))\n}\n\nfunc (c *core) withErrorReport(ent zapcore.Entry, fields []zapcore.Field) []zapcore.Field {\n\t// If the error report was manually set, don't overwrite it\n\tfor i := range fields {\n\t\tif fields[i].Key == contextKey {\n\t\t\treturn fields\n\t\t}\n\t}\n\n\tif !ent.Caller.Defined {\n\t\treturn fields\n\t}\n\n\treturn append(fields, ErrorReport(ent.Caller.PC, ent.Caller.File, ent.Caller.Line, true))\n}\n"
  },
  {
    "path": "core_test.go",
    "content": "package zapdriver\n\nimport (\n\t\"runtime\"\n\t\"strconv\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"go.uber.org/zap\"\n\t\"go.uber.org/zap/zapcore\"\n\t\"go.uber.org/zap/zaptest/observer\"\n)\n\nfunc TestWithLabels(t *testing.T) {\n\tfields := []zap.Field{\n\t\tzap.String(\"hello\", \"world\"),\n\t\tLabel(\"one\", \"value\"),\n\t\tLabel(\"two\", \"value\"),\n\t}\n\n\tlabels := newLabels()\n\tlabels.store = map[string]string{\"one\": \"value\", \"two\": \"value\"}\n\n\twant := []zap.Field{\n\t\tzap.String(\"hello\", \"world\"),\n\t\tzap.Object(\"logging.googleapis.com/labels\", labels),\n\t}\n\n\tassert.Equal(t, want, (&core{}).withLabels(fields))\n}\n\nfunc TestExtractLabels(t *testing.T) {\n\tvar lbls *labels\n\tc := &core{\n\t\tCore:       zapcore.NewNopCore(),\n\t\tpermLabels: newLabels(),\n\t\ttempLabels: newLabels(),\n\t}\n\n\tfields := []zap.Field{\n\t\tzap.String(\"hello\", \"world\"),\n\t\tLabel(\"one\", \"world\"),\n\t\tLabel(\"two\", \"worlds\"),\n\t}\n\n\tlbls, fields = c.extractLabels(fields)\n\n\trequire.Len(t, lbls.store, 2)\n\n\tlbls.mutex.RLock()\n\tassert.Equal(t, \"world\", lbls.store[\"one\"])\n\tassert.Equal(t, \"worlds\", lbls.store[\"two\"])\n\tlbls.mutex.RUnlock()\n\n\trequire.Len(t, fields, 1)\n\tassert.Equal(t, zap.String(\"hello\", \"world\"), fields[0])\n}\n\nfunc TestWithSourceLocation(t *testing.T) {\n\tfields := []zap.Field{zap.String(\"hello\", \"world\")}\n\tpc, file, line, ok := runtime.Caller(0)\n\tent := zapcore.Entry{Caller: zapcore.NewEntryCaller(pc, file, line, ok)}\n\n\twant := []zap.Field{\n\t\tzap.String(\"hello\", \"world\"),\n\t\tzap.Object(sourceKey, newSource(pc, file, line, ok)),\n\t}\n\n\tassert.Equal(t, want, (&core{}).withSourceLocation(ent, fields))\n}\n\nfunc TestWithSourceLocation_DoesNotOverwrite(t *testing.T) {\n\tfields := []zap.Field{zap.String(sourceKey, \"world\")}\n\tpc, file, line, ok := runtime.Caller(0)\n\tent := zapcore.Entry{Caller: zapcore.NewEntryCaller(pc, file, line, ok)}\n\n\twant := []zap.Field{\n\t\tzap.String(sourceKey, \"world\"),\n\t}\n\n\tassert.Equal(t, want, (&core{}).withSourceLocation(ent, fields))\n}\n\nfunc TestWithSourceLocation_OnlyWhenDefined(t *testing.T) {\n\tfields := []zap.Field{zap.String(\"hello\", \"world\")}\n\tpc, file, line, ok := runtime.Caller(0)\n\tent := zapcore.Entry{Caller: zapcore.NewEntryCaller(pc, file, line, ok)}\n\tent.Caller.Defined = false\n\n\twant := []zap.Field{\n\t\tzap.String(\"hello\", \"world\"),\n\t}\n\n\tassert.Equal(t, want, (&core{}).withSourceLocation(ent, fields))\n}\n\nfunc TestWithErrorReport(t *testing.T) {\n\tfields := []zap.Field{zap.String(\"hello\", \"world\")}\n\tpc, file, line, ok := runtime.Caller(0)\n\tent := zapcore.Entry{Caller: zapcore.NewEntryCaller(pc, file, line, ok)}\n\n\twant := []zap.Field{\n\t\tzap.String(\"hello\", \"world\"),\n\t\tzap.Object(contextKey, newReportContext(pc, file, line, ok)),\n\t}\n\n\tassert.Equal(t, want, (&core{}).withErrorReport(ent, fields))\n}\n\nfunc TestWithErrorReport_DoesNotOverwrite(t *testing.T) {\n\tfields := []zap.Field{zap.String(contextKey, \"world\")}\n\tpc, file, line, ok := runtime.Caller(0)\n\tent := zapcore.Entry{Caller: zapcore.NewEntryCaller(pc, file, line, ok)}\n\n\twant := []zap.Field{\n\t\tzap.String(contextKey, \"world\"),\n\t}\n\n\tassert.Equal(t, want, (&core{}).withErrorReport(ent, fields))\n}\n\nfunc TestWithErrorReport_OnlyWhenDefined(t *testing.T) {\n\tfields := []zap.Field{zap.String(\"hello\", \"world\")}\n\tpc, file, line, ok := runtime.Caller(0)\n\tent := zapcore.Entry{Caller: zapcore.NewEntryCaller(pc, file, line, ok)}\n\tent.Caller.Defined = false\n\n\twant := []zap.Field{\n\t\tzap.String(\"hello\", \"world\"),\n\t}\n\n\tassert.Equal(t, want, (&core{}).withErrorReport(ent, fields))\n}\n\nfunc TestWithServiceContext(t *testing.T) {\n\tfields := []zap.Field{zap.String(\"hello\", \"world\")}\n\n\twant := []zap.Field{\n\t\tzap.String(\"hello\", \"world\"),\n\t\tzap.Object(serviceContextKey, newServiceContext(\"test service\")),\n\t}\n\n\tassert.Equal(t, want, (&core{}).withServiceContext(\"test service\", fields))\n}\n\nfunc TestWithServiceContext_DoesNotOverwrite(t *testing.T) {\n\tfields := []zap.Field{zap.String(serviceContextKey, \"world\")}\n\n\twant := []zap.Field{\n\t\tzap.String(serviceContextKey, \"world\"),\n\t}\n\n\tassert.Equal(t, want, (&core{}).withServiceContext(\"test service\", fields))\n}\n\nfunc TestWrite(t *testing.T) {\n\ttemp := newLabels()\n\ttemp.store = map[string]string{\"one\": \"1\", \"two\": \"2\"}\n\n\tdebugcore, logs := observer.New(zapcore.DebugLevel)\n\tcore := &core{\n\t\tCore:       debugcore,\n\t\tpermLabels: newLabels(),\n\t\ttempLabels: temp,\n\t}\n\n\tfields := []zap.Field{\n\t\tzap.String(\"hello\", \"world\"),\n\t\tLabel(\"one\", \"value\"),\n\t\tLabel(\"two\", \"value\"),\n\t}\n\n\terr := core.Write(zapcore.Entry{}, fields)\n\trequire.NoError(t, err)\n\n\tassert.NotNil(t, logs.All()[0].ContextMap()[labelsKey])\n}\n\nfunc TestWriteConcurrent(t *testing.T) {\n\ttemp := newLabels()\n\ttemp.store = map[string]string{\"one\": \"1\", \"two\": \"2\"}\n\tgoRoutines := 8\n\tcounter := int32(10000)\n\n\tdebugcore, logs := observer.New(zapcore.DebugLevel)\n\tcore := &core{\n\t\tCore:       debugcore,\n\t\tpermLabels: newLabels(),\n\t\ttempLabels: temp,\n\t}\n\n\tfields := []zap.Field{\n\t\tzap.String(\"hello\", \"world\"),\n\t\tLabel(\"one\", \"value\"),\n\t\tLabel(\"two\", \"value\"),\n\t}\n\n\tvar wg sync.WaitGroup\n\twg.Add(goRoutines)\n\tfor i := 0; i < goRoutines; i++ {\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\tfor atomic.AddInt32(&counter, -1) > 0 {\n\t\t\t\terr := core.Write(zapcore.Entry{}, fields)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t}\n\t\t}()\n\t}\n\twg.Wait()\n\n\tassert.NotNil(t, logs.All()[0].ContextMap()[labelsKey])\n}\n\nfunc TestWithAndWrite(t *testing.T) {\n\tdebugcore, logs := observer.New(zapcore.DebugLevel)\n\tcore := zapcore.Core(&core{\n\t\tCore:       debugcore,\n\t\tpermLabels: newLabels(),\n\t\ttempLabels: newLabels(),\n\t})\n\n\tcore = core.With([]zapcore.Field{Label(\"one\", \"world\")})\n\terr := core.Write(zapcore.Entry{}, []zapcore.Field{Label(\"two\", \"worlds\")})\n\trequire.NoError(t, err)\n\n\tlabels := logs.All()[0].ContextMap()[labelsKey].(map[string]interface{})\n\n\tassert.Equal(t, \"world\", labels[\"one\"])\n\tassert.Equal(t, \"worlds\", labels[\"two\"])\n}\n\nfunc TestWithAndWrite_MultipleEntries(t *testing.T) {\n\tdebugcore, logs := observer.New(zapcore.DebugLevel)\n\tcore := zapcore.Core(&core{\n\t\tCore:       debugcore,\n\t\tpermLabels: newLabels(),\n\t\ttempLabels: newLabels(),\n\t})\n\n\tcore = core.With([]zapcore.Field{Label(\"one\", \"world\")})\n\terr := core.Write(zapcore.Entry{}, []zapcore.Field{Label(\"two\", \"worlds\")})\n\trequire.NoError(t, err)\n\n\tlabels := logs.All()[0].ContextMap()[labelsKey].(map[string]interface{})\n\trequire.Len(t, labels, 2)\n\n\tassert.Equal(t, \"world\", labels[\"one\"])\n\tassert.Equal(t, \"worlds\", labels[\"two\"])\n\n\terr = core.Write(zapcore.Entry{}, []zapcore.Field{Label(\"three\", \"worlds\")})\n\trequire.NoError(t, err)\n\n\tlabels = logs.All()[1].ContextMap()[labelsKey].(map[string]interface{})\n\trequire.Len(t, labels, 2)\n\n\tassert.Equal(t, \"world\", labels[\"one\"])\n\tassert.Equal(t, \"worlds\", labels[\"three\"])\n}\n\nfunc TestWriteReportAllErrors(t *testing.T) {\n\tdebugcore, logs := observer.New(zapcore.DebugLevel)\n\tcore := zapcore.Core(&core{\n\t\tCore:       debugcore,\n\t\tpermLabels: newLabels(),\n\t\ttempLabels: newLabels(),\n\t\tconfig: driverConfig{\n\t\t\tReportAllErrors: true,\n\t\t},\n\t})\n\n\tpc, file, line, ok := runtime.Caller(0)\n\t// core.With should return with correct config\n\tcore = core.With([]zapcore.Field{Label(\"one\", \"world\")})\n\terr := core.Write(zapcore.Entry{\n\t\tLevel:  zapcore.ErrorLevel,\n\t\tCaller: zapcore.NewEntryCaller(pc, file, line, ok),\n\t}, []zapcore.Field{Label(\"two\", \"worlds\")})\n\trequire.NoError(t, err)\n\n\tcontext := logs.All()[0].ContextMap()[contextKey].(map[string]interface{})\n\trLocation := context[\"reportLocation\"].(map[string]interface{})\n\tassert.Contains(t, rLocation[\"filePath\"], \"zapdriver/core_test.go\")\n\tassert.Equal(t, strconv.Itoa(line), rLocation[\"lineNumber\"])\n\tassert.Contains(t, rLocation[\"functionName\"], \"zapdriver.TestWriteReportAllErrors\")\n\n\t// Assert that a service context was attached even though service name was not set\n\tserviceContext := logs.All()[0].ContextMap()[serviceContextKey].(map[string]interface{})\n\tassert.Equal(t, \"unknown\", serviceContext[\"service\"])\n}\n\nfunc TestWriteServiceContext(t *testing.T) {\n\tdebugcore, logs := observer.New(zapcore.DebugLevel)\n\tcore := zapcore.Core(&core{\n\t\tCore:       debugcore,\n\t\tpermLabels: newLabels(),\n\t\ttempLabels: newLabels(),\n\t\tconfig: driverConfig{\n\t\t\tServiceName: \"test service\",\n\t\t},\n\t})\n\n\terr := core.Write(zapcore.Entry{}, []zapcore.Field{})\n\trequire.NoError(t, err)\n\n\t// Assert that a service context was attached even though service name was not set\n\tserviceContext := logs.All()[0].ContextMap()[serviceContextKey].(map[string]interface{})\n\tassert.Equal(t, \"test service\", serviceContext[\"service\"])\n}\n\nfunc TestWriteReportAllErrors_WithServiceContext(t *testing.T) {\n\tdebugcore, logs := observer.New(zapcore.DebugLevel)\n\tcore := zapcore.Core(&core{\n\t\tCore:       debugcore,\n\t\tpermLabels: newLabels(),\n\t\ttempLabels: newLabels(),\n\t\tconfig: driverConfig{\n\t\t\tReportAllErrors: true,\n\t\t\tServiceName:     \"test service\",\n\t\t},\n\t})\n\n\tpc, file, line, ok := runtime.Caller(0)\n\terr := core.Write(zapcore.Entry{\n\t\tLevel:  zapcore.ErrorLevel,\n\t\tCaller: zapcore.NewEntryCaller(pc, file, line, ok),\n\t}, []zapcore.Field{})\n\trequire.NoError(t, err)\n\n\tassert.Contains(t, logs.All()[0].ContextMap(), contextKey)\n\n\t// Assert that a service context was attached even though service name was not set\n\tserviceContext := logs.All()[0].ContextMap()[serviceContextKey].(map[string]interface{})\n\tassert.Equal(t, \"test service\", serviceContext[\"service\"])\n}\n\nfunc TestWriteReportAllErrors_InfoLog(t *testing.T) {\n\tdebugcore, logs := observer.New(zapcore.DebugLevel)\n\tcore := zapcore.Core(&core{\n\t\tCore:       debugcore,\n\t\tpermLabels: newLabels(),\n\t\ttempLabels: newLabels(),\n\t\tconfig: driverConfig{\n\t\t\tReportAllErrors: true,\n\t\t},\n\t})\n\n\tpc, file, line, ok := runtime.Caller(0)\n\terr := core.Write(zapcore.Entry{\n\t\tLevel:  zapcore.InfoLevel,\n\t\tCaller: zapcore.NewEntryCaller(pc, file, line, ok),\n\t}, []zapcore.Field{})\n\trequire.NoError(t, err)\n\n\tassert.NotContains(t, logs.All()[0].ContextMap(), contextKey)\n\tassert.NotContains(t, logs.All()[0].ContextMap(), serviceContextKey)\n}\n\nfunc TestAllLabels(t *testing.T) {\n\tperm := newLabels()\n\tperm.store = map[string]string{\"one\": \"1\", \"two\": \"2\", \"three\": \"3\"}\n\n\ttemp := newLabels()\n\ttemp.store = map[string]string{\"one\": \"ONE\", \"three\": \"THREE\"}\n\n\tcore := &core{\n\t\tCore:       zapcore.NewNopCore(),\n\t\tpermLabels: perm,\n\t\ttempLabels: temp,\n\t}\n\n\tout := core.allLabels()\n\trequire.Len(t, out.store, 3)\n\n\tout.mutex.RLock()\n\tassert.Equal(t, out.store[\"one\"], \"ONE\")\n\tassert.Equal(t, out.store[\"two\"], \"2\")\n\tassert.Equal(t, out.store[\"three\"], \"THREE\")\n\tout.mutex.RUnlock()\n}\n"
  },
  {
    "path": "encoder.go",
    "content": "package zapdriver\n\nimport (\n\t\"time\"\n\n\t\"go.uber.org/zap/zapcore\"\n)\n\n// logLevelSeverity maps the Zap log levels to the correct level names as\n// defined by Stackdriver.\n//\n// DEFAULT     (0) The log entry has no assigned severity level.\n// DEBUG     (100) Debug or trace information.\n// INFO      (200) Routine information, such as ongoing status or performance.\n// NOTICE    (300) Normal but significant events, such as start up, shut down, or a configuration change.\n// WARNING   (400) Warning events might cause problems.\n// ERROR     (500) Error events are likely to cause problems.\n// CRITICAL  (600) Critical events cause more severe problems or outages.\n// ALERT     (700) A person must take an action immediately.\n// EMERGENCY (800) One or more systems are unusable.\n//\n// See: https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry#LogSeverity\nvar logLevelSeverity = map[zapcore.Level]string{\n\tzapcore.DebugLevel:  \"DEBUG\",\n\tzapcore.InfoLevel:   \"INFO\",\n\tzapcore.WarnLevel:   \"WARNING\",\n\tzapcore.ErrorLevel:  \"ERROR\",\n\tzapcore.DPanicLevel: \"CRITICAL\",\n\tzapcore.PanicLevel:  \"ALERT\",\n\tzapcore.FatalLevel:  \"EMERGENCY\",\n}\n\n// encoderConfig is the default encoder configuration, slightly tweaked to use\n// the correct fields for Stackdriver to parse them.\nvar encoderConfig = zapcore.EncoderConfig{\n\tTimeKey:        \"timestamp\",\n\tLevelKey:       \"severity\",\n\tNameKey:        \"logger\",\n\tCallerKey:      \"caller\",\n\tMessageKey:     \"message\",\n\tStacktraceKey:  \"stacktrace\",\n\tLineEnding:     zapcore.DefaultLineEnding,\n\tEncodeLevel:    EncodeLevel,\n\tEncodeTime:     RFC3339NanoTimeEncoder,\n\tEncodeDuration: zapcore.SecondsDurationEncoder,\n\tEncodeCaller:   zapcore.ShortCallerEncoder,\n}\n\n// EncodeLevel maps the internal Zap log level to the appropriate Stackdriver\n// level.\nfunc EncodeLevel(l zapcore.Level, enc zapcore.PrimitiveArrayEncoder) {\n\tenc.AppendString(logLevelSeverity[l])\n}\n\n// RFC3339NanoTimeEncoder serializes a time.Time to an RFC3339Nano-formatted\n// string with nanoseconds precision.\nfunc RFC3339NanoTimeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) {\n\tenc.AppendString(t.Format(time.RFC3339Nano))\n}\n"
  },
  {
    "path": "encoder_test.go",
    "content": "package zapdriver_test\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/blendle/zapdriver\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.uber.org/zap/zapcore\"\n)\n\nfunc TestEncodeLevel(t *testing.T) {\n\tt.Parallel()\n\n\tvar tests = []struct {\n\t\tlvl  zapcore.Level\n\t\twant string\n\t}{\n\t\t{zapcore.DebugLevel, \"DEBUG\"},\n\t\t{zapcore.InfoLevel, \"INFO\"},\n\t\t{zapcore.WarnLevel, \"WARNING\"},\n\t\t{zapcore.ErrorLevel, \"ERROR\"},\n\t\t{zapcore.DPanicLevel, \"CRITICAL\"},\n\t\t{zapcore.PanicLevel, \"ALERT\"},\n\t\t{zapcore.FatalLevel, \"EMERGENCY\"},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.want, func(t *testing.T) {\n\t\t\tenc := &sliceArrayEncoder{}\n\t\t\tzapdriver.EncodeLevel(tt.lvl, enc)\n\n\t\t\trequire.Len(t, enc.elems, 1)\n\t\t\tassert.Equal(t, enc.elems[0].(string), tt.want)\n\t\t})\n\t}\n}\n\nfunc TestRFC3339NanoTimeEncoder(t *testing.T) {\n\tt.Parallel()\n\n\tts := time.Date(2018, 4, 9, 12, 43, 12, 678359, time.UTC)\n\n\tenc := &sliceArrayEncoder{}\n\tzapdriver.RFC3339NanoTimeEncoder(ts, enc)\n\n\trequire.Len(t, enc.elems, 1)\n\tassert.Equal(t, ts.Format(time.RFC3339Nano), enc.elems[0].(string))\n}\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/blendle/zapdriver\n\ngo 1.13\n\nrequire (\n\tgithub.com/davecgh/go-spew v1.1.1 // indirect\n\tgithub.com/pkg/errors v0.8.1 // indirect\n\tgithub.com/stretchr/testify v1.3.0\n\tgo.uber.org/atomic v1.4.0 // indirect\n\tgo.uber.org/multierr v1.1.0 // indirect\n\tgo.uber.org/zap v1.10.0\n)\n"
  },
  {
    "path": "go.sum",
    "content": "github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=\ngithub.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=\ngithub.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=\ngo.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU=\ngo.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=\ngo.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI=\ngo.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=\ngo.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM=\ngo.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=\n"
  },
  {
    "path": "http.go",
    "content": "package zapdriver\n\n// \"Broker: Request timed out\"\n// https://console.cloud.google.com/logs/viewer?project=bnl-blendle&minLogLevel=\n// 0&expandAll=false&timestamp=2018-05-23T22:21:56.142000000Z&customFacets=&limi\n// tCustomFacetWidth=true&dateRangeEnd=2018-05-23T22:21:52.545Z&interval=PT1H&re\n// source=container%2Fcluster_name%2Fblendle-2%2Fnamespace_id%2Fstream-\n// composition-analytic-events-\n// backfill&scrollTimestamp=2018-05-23T05:29:33.000000000Z&logName=projects\n// %2Fbnl-blendle%2Flogs%2Fstream-composition-analytic-events-\n// pipe-1&dateRangeUnbound=backwardInTime\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"net/http\"\n\t\"strconv\"\n\n\t\"go.uber.org/zap\"\n\t\"go.uber.org/zap/zapcore\"\n)\n\n// HTTP adds the correct Stackdriver \"HTTP\" field.\n//\n// see: https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry#HttpRequest\nfunc HTTP(req *HTTPPayload) zap.Field {\n\treturn zap.Object(\"httpRequest\", req)\n}\n\n// HTTPPayload is the complete payload that can be interpreted by\n// Stackdriver as a HTTP request.\ntype HTTPPayload struct {\n\t// The request method. Examples: \"GET\", \"HEAD\", \"PUT\", \"POST\".\n\tRequestMethod string `json:\"requestMethod\"`\n\n\t// The scheme (http, https), the host name, the path and the query portion of\n\t// the URL that was requested.\n\t//\n\t// Example: \"http://example.com/some/info?color=red\".\n\tRequestURL string `json:\"requestUrl\"`\n\n\t// The size of the HTTP request message in bytes, including the request\n\t// headers and the request body.\n\tRequestSize string `json:\"requestSize\"`\n\n\t// The response code indicating the status of response.\n\t//\n\t// Examples: 200, 404.\n\tStatus int `json:\"status\"`\n\n\t// The size of the HTTP response message sent back to the client, in bytes,\n\t// including the response headers and the response body.\n\tResponseSize string `json:\"responseSize\"`\n\n\t// The user agent sent by the client.\n\t//\n\t// Example: \"Mozilla/4.0 (compatible; MSIE 6.0; Windows 98; Q312461; .NET CLR 1.0.3705)\".\n\tUserAgent string `json:\"userAgent\"`\n\n\t// The IP address (IPv4 or IPv6) of the client that issued the HTTP request.\n\t//\n\t// Examples: \"192.168.1.1\", \"FE80::0202:B3FF:FE1E:8329\".\n\tRemoteIP string `json:\"remoteIp\"`\n\n\t// The IP address (IPv4 or IPv6) of the origin server that the request was\n\t// sent to.\n\tServerIP string `json:\"serverIp\"`\n\n\t// The referrer URL of the request, as defined in HTTP/1.1 Header Field\n\t// Definitions.\n\tReferer string `json:\"referer\"`\n\n\t// The request processing latency on the server, from the time the request was\n\t// received until the response was sent.\n\t//\n\t// A duration in seconds with up to nine fractional digits, terminated by 's'.\n\t//\n\t// Example: \"3.5s\".\n\tLatency string `json:\"latency\"`\n\n\t// Whether or not a cache lookup was attempted.\n\tCacheLookup bool `json:\"cacheLookup\"`\n\n\t// Whether or not an entity was served from cache (with or without\n\t// validation).\n\tCacheHit bool `json:\"cacheHit\"`\n\n\t// Whether or not the response was validated with the origin server before\n\t// being served from cache. This field is only meaningful if cacheHit is True.\n\tCacheValidatedWithOriginServer bool `json:\"cacheValidatedWithOriginServer\"`\n\n\t// The number of HTTP response bytes inserted into cache. Set only when a\n\t// cache fill was attempted.\n\tCacheFillBytes string `json:\"cacheFillBytes\"`\n\n\t// Protocol used for the request.\n\t//\n\t// Examples: \"HTTP/1.1\", \"HTTP/2\", \"websocket\"\n\tProtocol string `json:\"protocol\"`\n}\n\n// NewHTTP returns a new HTTPPayload struct, based on the passed\n// in http.Request and http.Response objects.\nfunc NewHTTP(req *http.Request, res *http.Response) *HTTPPayload {\n\tif req == nil {\n\t\treq = &http.Request{}\n\t}\n\n\tif res == nil {\n\t\tres = &http.Response{}\n\t}\n\n\tsdreq := &HTTPPayload{\n\t\tRequestMethod: req.Method,\n\t\tStatus:        res.StatusCode,\n\t\tUserAgent:     req.UserAgent(),\n\t\tRemoteIP:      req.RemoteAddr,\n\t\tReferer:       req.Referer(),\n\t\tProtocol:      req.Proto,\n\t}\n\n\tif req.URL != nil {\n\t\tsdreq.RequestURL = req.URL.String()\n\t}\n\n\tbuf := &bytes.Buffer{}\n\tif req.Body != nil {\n\t\tn, _ := io.Copy(buf, req.Body) // nolint: gas\n\t\tsdreq.RequestSize = strconv.FormatInt(n, 10)\n\t}\n\n\tif res.Body != nil {\n\t\tbuf.Reset()\n\t\tn, _ := io.Copy(buf, res.Body) // nolint: gas\n\t\tsdreq.ResponseSize = strconv.FormatInt(n, 10)\n\t}\n\n\treturn sdreq\n}\n\n// MarshalLogObject implements zapcore.ObjectMarshaller interface.\nfunc (req HTTPPayload) MarshalLogObject(enc zapcore.ObjectEncoder) error {\n\tenc.AddString(\"requestMethod\", req.RequestMethod)\n\tenc.AddString(\"requestUrl\", req.RequestURL)\n\tenc.AddString(\"requestSize\", req.RequestSize)\n\tenc.AddInt(\"status\", req.Status)\n\tenc.AddString(\"responseSize\", req.ResponseSize)\n\tenc.AddString(\"userAgent\", req.UserAgent)\n\tenc.AddString(\"remoteIp\", req.RemoteIP)\n\tenc.AddString(\"serverIp\", req.ServerIP)\n\tenc.AddString(\"referer\", req.Referer)\n\tenc.AddString(\"latency\", req.Latency)\n\tenc.AddBool(\"cacheLookup\", req.CacheLookup)\n\tenc.AddBool(\"cacheHit\", req.CacheHit)\n\tenc.AddBool(\"cacheValidatedWithOriginServer\", req.CacheValidatedWithOriginServer)\n\tenc.AddString(\"cacheFillBytes\", req.CacheFillBytes)\n\tenc.AddString(\"protocol\", req.Protocol)\n\n\treturn nil\n}\n"
  },
  {
    "path": "http_test.go",
    "content": "package zapdriver_test\n\nimport (\n\t\"io/ioutil\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"net/url\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/blendle/zapdriver\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"go.uber.org/zap\"\n)\n\nfunc TestHTTP(t *testing.T) {\n\tt.Parallel()\n\n\treq := &zapdriver.HTTPPayload{}\n\tfield := zapdriver.HTTP(req)\n\n\tassert.Equal(t, zap.Object(\"httpRequest\", req), field)\n}\n\nfunc TestNewHTTP(t *testing.T) {\n\tt.Parallel()\n\n\tvar tests = map[string]struct {\n\t\treq  *http.Request\n\t\tres  *http.Response\n\t\twant *zapdriver.HTTPPayload\n\t}{\n\t\t\"empty\": {\n\t\t\tnil,\n\t\t\tnil,\n\t\t\t&zapdriver.HTTPPayload{},\n\t\t},\n\n\t\t\"RequestMethod\": {\n\t\t\t&http.Request{Method: \"GET\"},\n\t\t\tnil,\n\t\t\t&zapdriver.HTTPPayload{RequestMethod: \"GET\"},\n\t\t},\n\n\t\t\"Status\": {\n\t\t\tnil,\n\t\t\t&http.Response{StatusCode: 404},\n\t\t\t&zapdriver.HTTPPayload{Status: 404},\n\t\t},\n\n\t\t\"UserAgent\": {\n\t\t\t&http.Request{Header: http.Header{\"User-Agent\": []string{\"hello world\"}}},\n\t\t\tnil,\n\t\t\t&zapdriver.HTTPPayload{UserAgent: \"hello world\"},\n\t\t},\n\n\t\t\"RemoteIP\": {\n\t\t\t&http.Request{RemoteAddr: \"127.0.0.1\"},\n\t\t\tnil,\n\t\t\t&zapdriver.HTTPPayload{RemoteIP: \"127.0.0.1\"},\n\t\t},\n\n\t\t\"Referrer\": {\n\t\t\t&http.Request{Header: http.Header{\"Referer\": []string{\"hello universe\"}}},\n\t\t\tnil,\n\t\t\t&zapdriver.HTTPPayload{Referer: \"hello universe\"},\n\t\t},\n\n\t\t\"Protocol\": {\n\t\t\t&http.Request{Proto: \"HTTP/1.1\"},\n\t\t\tnil,\n\t\t\t&zapdriver.HTTPPayload{Protocol: \"HTTP/1.1\"},\n\t\t},\n\n\t\t\"RequestURL\": {\n\t\t\t&http.Request{URL: &url.URL{Host: \"example.com\", Scheme: \"https\"}},\n\t\t\tnil,\n\t\t\t&zapdriver.HTTPPayload{RequestURL: \"https://example.com\"},\n\t\t},\n\n\t\t\"RequestSize\": {\n\t\t\t&http.Request{Body: ioutil.NopCloser(strings.NewReader(\"12345\"))},\n\t\t\tnil,\n\t\t\t&zapdriver.HTTPPayload{RequestSize: \"5\"},\n\t\t},\n\n\t\t\"ResponseSize\": {\n\t\t\tnil,\n\t\t\t&http.Response{Body: ioutil.NopCloser(strings.NewReader(\"12345\"))},\n\t\t\t&zapdriver.HTTPPayload{ResponseSize: \"5\"},\n\t\t},\n\n\t\t\"simple request\": {\n\t\t\thttptest.NewRequest(\"POST\", \"/\", strings.NewReader(\"12345\")),\n\t\t\tnil,\n\t\t\t&zapdriver.HTTPPayload{\n\t\t\t\tRequestSize:   \"5\",\n\t\t\t\tRequestMethod: \"POST\",\n\t\t\t\tRemoteIP:      \"192.0.2.1:1234\",\n\t\t\t\tProtocol:      \"HTTP/1.1\",\n\t\t\t\tRequestURL:    \"/\",\n\t\t\t},\n\t\t},\n\n\t\t\"simple response\": {\n\t\t\tnil,\n\t\t\t&http.Response{Body: ioutil.NopCloser(strings.NewReader(\"12345\")), StatusCode: 404},\n\t\t\t&zapdriver.HTTPPayload{ResponseSize: \"5\", Status: 404},\n\t\t},\n\n\t\t\"request & response\": {\n\t\t\t&http.Request{Method: \"POST\", Proto: \"HTTP/1.1\"},\n\t\t\t&http.Response{StatusCode: 200},\n\t\t\t&zapdriver.HTTPPayload{RequestMethod: \"POST\", Protocol: \"HTTP/1.1\", Status: 200},\n\t\t},\n\t}\n\n\tfor name, tt := range tests {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tassert.Equal(t, tt.want, zapdriver.NewHTTP(tt.req, tt.res))\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "label.go",
    "content": "package zapdriver\n\nimport (\n\t\"strings\"\n\t\"sync\"\n\n\t\"go.uber.org/zap\"\n\t\"go.uber.org/zap/zapcore\"\n)\n\nconst labelsKey = \"logging.googleapis.com/labels\"\n\n// Label adds an optional label to the payload.\n//\n// Labels are a set of user-defined (key, value) data that provides additional\n// information about the log entry.\n//\n// Example: { \"name\": \"wrench\", \"mass\": \"1.3kg\", \"count\": \"3\" }.\nfunc Label(key, value string) zap.Field {\n\treturn zap.String(\"labels.\"+key, value)\n}\n\n// Labels takes Zap fields, filters the ones that have their key start with the\n// string `labels.` and their value type set to StringType. It then wraps those\n// key/value pairs in a top-level `labels` namespace.\nfunc Labels(fields ...zap.Field) zap.Field {\n\tlbls := newLabels()\n\n\tlbls.mutex.Lock()\n\tfor i := range fields {\n\t\tif isLabelField(fields[i]) {\n\t\t\tlbls.store[strings.Replace(fields[i].Key, \"labels.\", \"\", 1)] = fields[i].String\n\t\t}\n\t}\n\tlbls.mutex.Unlock()\n\n\treturn labelsField(lbls)\n}\n\nfunc isLabelField(field zap.Field) bool {\n\treturn strings.HasPrefix(field.Key, \"labels.\") && field.Type == zapcore.StringType\n}\n\nfunc labelsField(l *labels) zap.Field {\n\treturn zap.Object(labelsKey, l)\n}\n\ntype labels struct {\n\tstore map[string]string\n\tmutex *sync.RWMutex\n}\n\nfunc newLabels() *labels {\n\treturn &labels{store: map[string]string{}, mutex: &sync.RWMutex{}}\n}\n\nfunc (l *labels) Add(key, value string) {\n\tl.mutex.Lock()\n\tl.store[key] = value\n\tl.mutex.Unlock()\n}\n\nfunc (l *labels) reset() {\n\tl.mutex.Lock()\n\tl.store = map[string]string{}\n\tl.mutex.Unlock()\n}\n\nfunc (l labels) MarshalLogObject(enc zapcore.ObjectEncoder) error {\n\tl.mutex.RLock()\n\tfor k, v := range l.store {\n\t\tenc.AddString(k, v)\n\t}\n\tl.mutex.RUnlock()\n\n\treturn nil\n}\n"
  },
  {
    "path": "label_test.go",
    "content": "package zapdriver\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"go.uber.org/zap\"\n)\n\nfunc TestLabel(t *testing.T) {\n\tt.Parallel()\n\n\tfield := Label(\"key\", \"value\")\n\n\tassert.Equal(t, zap.String(\"labels.key\", \"value\"), field)\n}\n\nfunc TestLabels(t *testing.T) {\n\tt.Parallel()\n\n\tfield := Labels(\n\t\tLabel(\"hello\", \"world\"),\n\t\tLabel(\"hi\", \"universe\"),\n\t)\n\n\tlabels := newLabels()\n\tlabels.store = map[string]string{\"hello\": \"world\", \"hi\": \"universe\"}\n\n\tassert.Equal(t, zap.Object(labelsKey, labels), field)\n}\n"
  },
  {
    "path": "logger.go",
    "content": "package zapdriver\n\nimport (\n\t\"go.uber.org/zap\"\n)\n\n// NewProduction builds a sensible production Logger that writes InfoLevel and\n// above logs to standard error as JSON.\n//\n// It's a shortcut for NewProductionConfig().Build(...Option).\nfunc NewProduction(options ...zap.Option) (*zap.Logger, error) {\n\toptions = append(options, WrapCore())\n\n\treturn NewProductionConfig().Build(options...)\n}\n\n// NewProductionWithCore is same as NewProduction but accepts a custom configured core\nfunc NewProductionWithCore(core zap.Option, options ...zap.Option) (*zap.Logger, error) {\n\toptions = append(options, core)\n\n\treturn NewProductionConfig().Build(options...)\n}\n\n// NewDevelopment builds a development Logger that writes DebugLevel and above\n// logs to standard error in a human-friendly format.\n//\n// It's a shortcut for NewDevelopmentConfig().Build(...Option).\nfunc NewDevelopment(options ...zap.Option) (*zap.Logger, error) {\n\toptions = append(options, WrapCore())\n\n\treturn NewDevelopmentConfig().Build(options...)\n}\n\n// NewDevelopmentWithCore is same as NewDevelopment but accepts a custom configured core\nfunc NewDevelopmentWithCore(core zap.Option, options ...zap.Option) (*zap.Logger, error) {\n\toptions = append(options, core)\n\n\treturn NewDevelopmentConfig().Build(options...)\n}\n"
  },
  {
    "path": "logger_test.go",
    "content": "package zapdriver\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"go.uber.org/zap\"\n)\n\nfunc TestNewProduction(t *testing.T) {\n\tlogger, err := NewProduction(zap.Fields(zap.String(\"hello\", \"world\")))\n\n\trequire.NoError(t, err)\n\tassert.IsType(t, &zap.Logger{}, logger)\n}\n\nfunc TestNewProductionWithCore(t *testing.T) {\n\tlogger, err := NewProductionWithCore(\n\t\tWrapCore(ReportAllErrors(true)),\n\t\tzap.Fields(zap.String(\"hello\", \"world\")),\n\t)\n\n\trequire.NoError(t, err)\n\tassert.IsType(t, &zap.Logger{}, logger)\n}\n\nfunc TestNewDevelopment(t *testing.T) {\n\tlogger, err := NewDevelopment(zap.Fields(zap.String(\"hello\", \"world\")))\n\n\trequire.NoError(t, err)\n\tassert.IsType(t, &zap.Logger{}, logger)\n}\n\nfunc TestNewDevelopmentWithCore(t *testing.T) {\n\tlogger, err := NewDevelopmentWithCore(\n\t\tWrapCore(ReportAllErrors(true)),\n\t\tzap.Fields(zap.String(\"hello\", \"world\")),\n\t)\n\n\trequire.NoError(t, err)\n\tassert.IsType(t, &zap.Logger{}, logger)\n}\n"
  },
  {
    "path": "operation.go",
    "content": "package zapdriver\n\nimport (\n\t\"go.uber.org/zap\"\n\t\"go.uber.org/zap/zapcore\"\n)\n\nconst operationKey = \"logging.googleapis.com/operation\"\n\n// Operation adds the correct Stackdriver \"operation\" field.\n//\n// Additional information about a potentially long-running operation with which\n// a log entry is associated.\n//\n// see: https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry#LogEntryOperation\nfunc Operation(id, producer string, first, last bool) zap.Field {\n\top := &operation{\n\t\tID:       id,\n\t\tProducer: producer,\n\t\tFirst:    first,\n\t\tLast:     last,\n\t}\n\n\treturn zap.Object(operationKey, op)\n}\n\n// OperationStart is a convenience function for `Operation`. It should be called\n// for the first operation log.\nfunc OperationStart(id, producer string) zap.Field {\n\treturn Operation(id, producer, true, false)\n}\n\n// OperationCont is a convenience function for `Operation`. It should be called\n// for any non-start/end operation log.\nfunc OperationCont(id, producer string) zap.Field {\n\treturn Operation(id, producer, false, false)\n}\n\n// OperationEnd is a convenience function for `Operation`. It should be called\n// for the last operation log.\nfunc OperationEnd(id, producer string) zap.Field {\n\treturn Operation(id, producer, false, true)\n}\n\n// operation is the complete payload that can be interpreted by Stackdriver as\n// an operation.\ntype operation struct {\n\t// Optional. An arbitrary operation identifier. Log entries with the same\n\t// identifier are assumed to be part of the same operation.\n\tID string `json:\"id\"`\n\n\t// Optional. An arbitrary producer identifier. The combination of id and\n\t// producer must be globally unique. Examples for producer:\n\t// \"MyDivision.MyBigCompany.com\", \"github.com/MyProject/MyApplication\".\n\tProducer string `json:\"producer\"`\n\n\t// Optional. Set this to True if this is the first log entry in the operation.\n\tFirst bool `json:\"first\"`\n\n\t// Optional. Set this to True if this is the last log entry in the operation.\n\tLast bool `json:\"last\"`\n}\n\n// MarshalLogObject implements zapcore.ObjectMarshaller interface.\nfunc (op operation) MarshalLogObject(enc zapcore.ObjectEncoder) error {\n\tenc.AddString(\"id\", op.ID)\n\tenc.AddString(\"producer\", op.Producer)\n\tenc.AddBool(\"first\", op.First)\n\tenc.AddBool(\"last\", op.Last)\n\n\treturn nil\n}\n"
  },
  {
    "path": "operation_test.go",
    "content": "package zapdriver\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"go.uber.org/zap\"\n)\n\nfunc TestOperation(t *testing.T) {\n\tt.Parallel()\n\n\top := &operation{ID: \"id\", Producer: \"producer\", First: true, Last: false}\n\tfield := Operation(\"id\", \"producer\", true, false)\n\n\tassert.Equal(t, zap.Object(operationKey, op), field)\n}\n\nfunc TestOperationStart(t *testing.T) {\n\tt.Parallel()\n\n\top := &operation{ID: \"id\", Producer: \"producer\", First: true, Last: false}\n\tfield := OperationStart(\"id\", \"producer\")\n\n\tassert.Equal(t, zap.Object(operationKey, op), field)\n}\n\nfunc TestOperationCont(t *testing.T) {\n\tt.Parallel()\n\n\top := &operation{ID: \"id\", Producer: \"producer\", First: false, Last: false}\n\tfield := OperationCont(\"id\", \"producer\")\n\n\tassert.Equal(t, zap.Object(operationKey, op), field)\n}\n\nfunc TestOperationEnd(t *testing.T) {\n\tt.Parallel()\n\n\top := &operation{ID: \"id\", Producer: \"producer\", First: false, Last: true}\n\tfield := OperationEnd(\"id\", \"producer\")\n\n\tassert.Equal(t, zap.Object(operationKey, op), field)\n}\n"
  },
  {
    "path": "report.go",
    "content": "package zapdriver\n\nimport (\n\t\"runtime\"\n\t\"strconv\"\n\n\t\"go.uber.org/zap\"\n\t\"go.uber.org/zap/zapcore\"\n)\n\nconst contextKey = \"context\"\n\n// ErrorReport adds the correct Stackdriver \"context\" field for getting the log line\n// reported as error.\n//\n// see: https://cloud.google.com/error-reporting/docs/formatting-error-messages\nfunc ErrorReport(pc uintptr, file string, line int, ok bool) zap.Field {\n\treturn zap.Object(contextKey, newReportContext(pc, file, line, ok))\n}\n\n// reportLocation is the source code location information associated with the log entry\n// for the purpose of reporting an error,\n// if any.\ntype reportLocation struct {\n\tFile     string `json:\"filePath\"`\n\tLine     string `json:\"lineNumber\"`\n\tFunction string `json:\"functionName\"`\n}\n\n// MarshalLogObject implements zapcore.ObjectMarshaller interface.\nfunc (location reportLocation) MarshalLogObject(enc zapcore.ObjectEncoder) error {\n\tenc.AddString(\"filePath\", location.File)\n\tenc.AddString(\"lineNumber\", location.Line)\n\tenc.AddString(\"functionName\", location.Function)\n\n\treturn nil\n}\n\n// reportContext is the context information attached to a log for reporting errors\ntype reportContext struct {\n\tReportLocation reportLocation `json:\"reportLocation\"`\n}\n\n// MarshalLogObject implements zapcore.ObjectMarshaller interface.\nfunc (context reportContext) MarshalLogObject(enc zapcore.ObjectEncoder) error {\n\tenc.AddObject(\"reportLocation\", context.ReportLocation)\n\n\treturn nil\n}\n\nfunc newReportContext(pc uintptr, file string, line int, ok bool) *reportContext {\n\tif !ok {\n\t\treturn nil\n\t}\n\n\tvar function string\n\tif fn := runtime.FuncForPC(pc); fn != nil {\n\t\tfunction = fn.Name()\n\t}\n\n\tcontext := &reportContext{\n\t\tReportLocation: reportLocation{\n\t\t\tFile:     file,\n\t\t\tLine:     strconv.Itoa(line),\n\t\t\tFunction: function,\n\t\t},\n\t}\n\n\treturn context\n}\n"
  },
  {
    "path": "report_test.go",
    "content": "package zapdriver\n\nimport (\n\t\"runtime\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestErrorReport(t *testing.T) {\n\tt.Parallel()\n\n\tgot := ErrorReport(runtime.Caller(0)).Interface.(*reportContext)\n\n\tassert.Contains(t, got.ReportLocation.File, \"zapdriver/report_test.go\")\n\tassert.Equal(t, \"13\", got.ReportLocation.Line)\n\tassert.Contains(t, got.ReportLocation.Function, \"zapdriver.TestErrorReport\")\n}\n\nfunc TestNewReportContext(t *testing.T) {\n\tt.Parallel()\n\n\tgot := newReportContext(runtime.Caller(0))\n\n\tassert.Contains(t, got.ReportLocation.File, \"zapdriver/report_test.go\")\n\tassert.Equal(t, \"23\", got.ReportLocation.Line)\n\tassert.Contains(t, got.ReportLocation.Function, \"zapdriver.TestNewReportContext\")\n}\n"
  },
  {
    "path": "service.go",
    "content": "package zapdriver\n\nimport (\n\t\"go.uber.org/zap\"\n\t\"go.uber.org/zap/zapcore\"\n)\n\nconst serviceContextKey = \"serviceContext\"\n\n// ServiceContext adds the correct service information adding the log line\n// It is a required field if an error needs to be reported.\n//\n// see: https://cloud.google.com/error-reporting/reference/rest/v1beta1/ServiceContext\n// see: https://cloud.google.com/error-reporting/docs/formatting-error-messages\nfunc ServiceContext(name string) zap.Field {\n\treturn zap.Object(serviceContextKey, newServiceContext(name))\n}\n\n// serviceContext describes a running service that sends errors.\n// Currently it only describes a service name.\ntype serviceContext struct {\n\tName string `json:\"service\"`\n}\n\n// MarshalLogObject implements zapcore.ObjectMarshaller interface.\nfunc (service_context serviceContext) MarshalLogObject(enc zapcore.ObjectEncoder) error {\n\tenc.AddString(\"service\", service_context.Name)\n\n\treturn nil\n}\n\nfunc newServiceContext(name string) *serviceContext {\n\treturn &serviceContext{\n\t\tName: name,\n\t}\n}\n"
  },
  {
    "path": "service_test.go",
    "content": "package zapdriver\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestServiceContext(t *testing.T) {\n\tt.Parallel()\n\n\tgot := ServiceContext(\"test service name\").Interface.(*serviceContext)\n\n\tassert.Equal(t, \"test service name\", got.Name)\n}\n\nfunc TestNewServiceContext(t *testing.T) {\n\tt.Parallel()\n\n\tgot := newServiceContext(\"test service name\")\n\n\tassert.Equal(t, \"test service name\", got.Name)\n}\n"
  },
  {
    "path": "source.go",
    "content": "package zapdriver\n\nimport (\n\t\"runtime\"\n\t\"strconv\"\n\n\t\"go.uber.org/zap\"\n\t\"go.uber.org/zap/zapcore\"\n)\n\nconst sourceKey = \"logging.googleapis.com/sourceLocation\"\n\n// SourceLocation adds the correct Stackdriver \"SourceLocation\" field.\n//\n// see: https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry#LogEntrySourceLocation\nfunc SourceLocation(pc uintptr, file string, line int, ok bool) zap.Field {\n\treturn zap.Object(sourceKey, newSource(pc, file, line, ok))\n}\n\n// source is the source code location information associated with the log entry,\n// if any.\ntype source struct {\n\t// Optional. Source file name. Depending on the runtime environment, this\n\t// might be a simple name or a fully-qualified name.\n\tFile string `json:\"file\"`\n\n\t// Optional. Line within the source file. 1-based; 0 indicates no line number\n\t// available.\n\tLine string `json:\"line\"`\n\n\t// Optional. Human-readable name of the function or method being invoked, with\n\t// optional context such as the class or package name. This information may be\n\t// used in contexts such as the logs viewer, where a file and line number are\n\t// less meaningful.\n\t//\n\t// The format should be dir/package.func.\n\tFunction string `json:\"function\"`\n}\n\n// MarshalLogObject implements zapcore.ObjectMarshaller interface.\nfunc (source source) MarshalLogObject(enc zapcore.ObjectEncoder) error {\n\tenc.AddString(\"file\", source.File)\n\tenc.AddString(\"line\", source.Line)\n\tenc.AddString(\"function\", source.Function)\n\n\treturn nil\n}\n\nfunc newSource(pc uintptr, file string, line int, ok bool) *source {\n\tif !ok {\n\t\treturn nil\n\t}\n\n\tvar function string\n\tif fn := runtime.FuncForPC(pc); fn != nil {\n\t\tfunction = fn.Name()\n\t}\n\n\tsource := &source{\n\t\tFile:     file,\n\t\tLine:     strconv.Itoa(line),\n\t\tFunction: function,\n\t}\n\n\treturn source\n}\n"
  },
  {
    "path": "source_test.go",
    "content": "package zapdriver\n\nimport (\n\t\"runtime\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestSourceLocation(t *testing.T) {\n\tt.Parallel()\n\n\tgot := SourceLocation(runtime.Caller(0)).Interface.(*source)\n\n\tassert.Contains(t, got.File, \"zapdriver/source_test.go\")\n\tassert.Equal(t, \"13\", got.Line)\n\tassert.Contains(t, got.Function, \"zapdriver.TestSourceLocation\")\n}\n\nfunc TestNewSource(t *testing.T) {\n\tt.Parallel()\n\n\tgot := newSource(runtime.Caller(0))\n\n\tassert.Contains(t, got.File, \"zapdriver/source_test.go\")\n\tassert.Equal(t, \"23\", got.Line)\n\tassert.Contains(t, got.Function, \"zapdriver.TestNewSource\")\n}\n"
  },
  {
    "path": "trace.go",
    "content": "package zapdriver\n\nimport (\n\t\"fmt\"\n\n\t\"go.uber.org/zap\"\n)\n\nconst (\n\ttraceKey        = \"logging.googleapis.com/trace\"\n\tspanKey         = \"logging.googleapis.com/spanId\"\n\ttraceSampledKey = \"logging.googleapis.com/trace_sampled\"\n)\n\n// TraceContext adds the correct Stackdriver \"trace\", \"span\", \"trace_sampled fields\n//\n// see: https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry\nfunc TraceContext(trace string, spanId string, sampled bool, projectName string) []zap.Field {\n\treturn []zap.Field{\n\t\tzap.String(traceKey, fmt.Sprintf(\"projects/%s/traces/%s\", projectName, trace)),\n\t\tzap.String(spanKey, spanId),\n\t\tzap.Bool(traceSampledKey, sampled),\n\t}\n}\n"
  },
  {
    "path": "trace_test.go",
    "content": "package zapdriver\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"go.uber.org/zap\"\n)\n\nfunc TestTraceContext(t *testing.T) {\n\tt.Parallel()\n\n\tfields := TraceContext(\"105445aa7843bc8bf206b120001000\", \"0\", true, \"my-project-name\")\n\tassert.Equal(t, fields, []zap.Field{\n\t\tzap.String(traceKey, \"projects/my-project-name/traces/105445aa7843bc8bf206b120001000\"),\n\t\tzap.String(spanKey, \"0\"),\n\t\tzap.Bool(traceSampledKey, true),\n\t})\n}\n"
  }
]