From b1247d2d0d51108c910a73891ff3116e5f032ab1 Mon Sep 17 00:00:00 2001 From: "Kali Kaneko (leap communications)" Date: Sat, 12 Jan 2019 18:39:45 +0100 Subject: [pkg] all your deps are vendored to us --- vendor/github.com/getlantern/context/LICENSE | 202 ++++++++ vendor/github.com/getlantern/context/README.md | 6 + vendor/github.com/getlantern/context/context.go | 309 +++++++++++ .../github.com/getlantern/context/context_test.go | 160 ++++++ vendor/github.com/getlantern/context/gotrack.go | 130 +++++ vendor/github.com/getlantern/errors/errors.go | 566 +++++++++++++++++++++ vendor/github.com/getlantern/errors/errors_test.go | 142 ++++++ vendor/github.com/getlantern/errors/hide.go | 50 ++ vendor/github.com/getlantern/golog/LICENSE | 202 ++++++++ vendor/github.com/getlantern/golog/README.md | 6 + vendor/github.com/getlantern/golog/golog.go | 458 +++++++++++++++++ vendor/github.com/getlantern/golog/golog_test.go | 224 ++++++++ vendor/github.com/getlantern/hex/hex.go | 107 ++++ vendor/github.com/getlantern/hidden/hidden.go | 66 +++ vendor/github.com/getlantern/hidden/hidden_test.go | 33 ++ vendor/github.com/getlantern/ops/ops.go | 154 ++++++ vendor/github.com/getlantern/ops/ops_test.go | 76 +++ 17 files changed, 2891 insertions(+) create mode 100644 vendor/github.com/getlantern/context/LICENSE create mode 100644 vendor/github.com/getlantern/context/README.md create mode 100644 vendor/github.com/getlantern/context/context.go create mode 100644 vendor/github.com/getlantern/context/context_test.go create mode 100644 vendor/github.com/getlantern/context/gotrack.go create mode 100644 vendor/github.com/getlantern/errors/errors.go create mode 100644 vendor/github.com/getlantern/errors/errors_test.go create mode 100644 vendor/github.com/getlantern/errors/hide.go create mode 100644 vendor/github.com/getlantern/golog/LICENSE create mode 100644 vendor/github.com/getlantern/golog/README.md create mode 100644 vendor/github.com/getlantern/golog/golog.go create mode 100644 vendor/github.com/getlantern/golog/golog_test.go create mode 100644 vendor/github.com/getlantern/hex/hex.go create mode 100644 vendor/github.com/getlantern/hidden/hidden.go create mode 100644 vendor/github.com/getlantern/hidden/hidden_test.go create mode 100644 vendor/github.com/getlantern/ops/ops.go create mode 100644 vendor/github.com/getlantern/ops/ops_test.go (limited to 'vendor/github.com/getlantern') diff --git a/vendor/github.com/getlantern/context/LICENSE b/vendor/github.com/getlantern/context/LICENSE new file mode 100644 index 0000000..5dc6c26 --- /dev/null +++ b/vendor/github.com/getlantern/context/LICENSE @@ -0,0 +1,202 @@ + + 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: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) 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 + + (d) 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 2016 Brave New Software Project, Inc. + + 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/vendor/github.com/getlantern/context/README.md b/vendor/github.com/getlantern/context/README.md new file mode 100644 index 0000000..978c9e0 --- /dev/null +++ b/vendor/github.com/getlantern/context/README.md @@ -0,0 +1,6 @@ +# context [![Travis CI Status](https://travis-ci.org/getlantern/context.svg?branch=master)](https://travis-ci.org/getlantern/context) [![Coverage Status](https://coveralls.io/repos/getlantern/context/badge.png?branch=master)](https://coveralls.io/r/getlantern/context) + +Provides goroutine-based context state inspired by https://github.com/tylerb/gls +and https://github.com/jtolds/gls. It uses the same basic hack as tylerb's +library, but adds a stack abstraction that allows nested contexts similar to +jtolds' library, but using `Enter()` and `Exit()` instead of callback functions. diff --git a/vendor/github.com/getlantern/context/context.go b/vendor/github.com/getlantern/context/context.go new file mode 100644 index 0000000..c77f129 --- /dev/null +++ b/vendor/github.com/getlantern/context/context.go @@ -0,0 +1,309 @@ +// Package context provides a mechanism for transparently tracking contextual +// state associated to the current goroutine and even across goroutines. +package context + +import ( + "sync" +) + +// Manager provides the ability to create and access Contexts. +type Manager interface { + // Enter enters a new level on the current Context stack, creating a new Context + // if necessary. + Enter() Context + + // Go starts the given function on a new goroutine but sharing the context of + // the current goroutine (if it has one). + Go(func()) + + // PutGlobal puts the given key->value pair into the global context. + PutGlobal(key string, value interface{}) + + // PutGlobalDynamic puts a key->value pair into the global context where the + // value is generated by a function that gets evaluated at every Read. If the + // value is a map[string]interface{}, we will unpack the map and set each + // contained key->value pair independently. + PutGlobalDynamic(key string, valueFN func() interface{}) + + // AsMap returns a map containing all values from the supplied obj if it is a + // Contextual, plus any addition values from along the stack, plus globals if so + // specified. + AsMap(obj interface{}, includeGlobals bool) Map +} + +type manager struct { + contexts map[uint64]*context + mxContexts sync.RWMutex + global Map + mxGlobal sync.RWMutex +} + +// NewManager creates a new Manager +func NewManager() Manager { + return &manager{ + contexts: make(map[uint64]*context), + global: make(Map), + } +} + +// Contextual is an interface for anything that maintains its own context. +type Contextual interface { + // Fill fills the given Map with all of this Contextual's context + Fill(m Map) +} + +// Map is a map of key->value pairs. +type Map map[string]interface{} + +// Fill implements the method from the Contextual interface. +func (_m Map) Fill(m Map) { + for key, value := range _m { + m[key] = value + } +} + +// Context is a context containing key->value pairs +type Context interface { + // Enter enters a new level on this Context stack. + Enter() Context + + // Go starts the given function on a new goroutine. + Go(fn func()) + + // Exit exits the current level on this Context stack. + Exit() + + // Put puts a key->value pair into the current level of the context stack. + Put(key string, value interface{}) Context + + // PutIfAbsent puts the given key->value pair into the current level of the + // context stack if and only if that key is defined nowhere within the context + // stack (including parent contexts). + PutIfAbsent(key string, value interface{}) Context + + // PutDynamic puts a key->value pair into the current level of the context + // stack where the value is generated by a function that gets evaluated at + // every Read. If the value is a map[string]interface{}, we will unpack the + // map and set each contained key->value pair independently. + PutDynamic(key string, valueFN func() interface{}) Context + + // Fill fills the given map with data from this Context + Fill(m Map) + + // AsMap returns a map containing all values from the supplied obj if it is a + // Contextual, plus any addition values from along the stack, plus globals if + // so specified. + AsMap(obj interface{}, includeGlobals bool) Map +} + +type context struct { + cm *manager + id uint64 + parent *context + branchedFrom *context + data Map + mx sync.RWMutex +} + +type dynval struct { + fn func() interface{} +} + +func (cm *manager) Enter() Context { + return cm.enter(curGoroutineID()) +} + +func (cm *manager) enter(id uint64) *context { + cm.mxContexts.Lock() + parentOrNil := cm.contexts[id] + c := cm.makeContext(id, parentOrNil, nil) + cm.contexts[id] = c + cm.mxContexts.Unlock() + return c +} + +func (cm *manager) exit(id uint64, parent *context) { + cm.mxContexts.Lock() + if parent == nil { + delete(cm.contexts, id) + } else { + cm.contexts[id] = parent + } + cm.mxContexts.Unlock() +} + +func (cm *manager) branch(id uint64, from *context) { + next := cm.makeContext(id, nil, from) + cm.mxContexts.Lock() + cm.contexts[id] = next + cm.mxContexts.Unlock() +} + +func (cm *manager) merge(id uint64) { + cm.mxContexts.Lock() + delete(cm.contexts, id) + cm.mxContexts.Unlock() +} + +func (c *context) Enter() Context { + c.mx.RLock() + id := c.id + c.mx.RUnlock() + return c.cm.enter(id) +} + +func (c *context) Go(fn func()) { + go func() { + id := curGoroutineID() + c.cm.branch(id, c) + fn() + c.cm.merge(id) + }() +} + +func (cm *manager) Go(fn func()) { + c := cm.currentContext() + if c != nil { + c.Go(fn) + } else { + go fn() + } +} + +func (cm *manager) makeContext(id uint64, parent *context, branchedFrom *context) *context { + return &context{ + cm: cm, + id: id, + parent: parent, + branchedFrom: branchedFrom, + data: make(Map), + } +} + +func (c *context) Exit() { + c.mx.RLock() + id := c.id + parent := c.parent + c.mx.RUnlock() + c.cm.exit(id, parent) +} + +func (c *context) Put(key string, value interface{}) Context { + c.mx.Lock() + c.data[key] = value + c.mx.Unlock() + return c +} + +func (c *context) PutIfAbsent(key string, value interface{}) Context { + for ctx := c; ctx != nil; { + ctx.mx.RLock() + _, exists := ctx.data[key] + next := ctx.parent + if next == nil { + next = ctx.branchedFrom + } + ctx.mx.RUnlock() + if exists { + return c + } + ctx = next + } + + // Value not set, set it + return c.Put(key, value) +} + +func (c *context) PutDynamic(key string, valueFN func() interface{}) Context { + value := &dynval{valueFN} + c.mx.Lock() + c.data[key] = value + c.mx.Unlock() + return c +} + +func (cm *manager) PutGlobal(key string, value interface{}) { + cm.mxGlobal.Lock() + cm.global[key] = value + cm.mxGlobal.Unlock() +} + +func (cm *manager) PutGlobalDynamic(key string, valueFN func() interface{}) { + value := &dynval{valueFN} + cm.mxGlobal.Lock() + cm.global[key] = value + cm.mxGlobal.Unlock() +} + +func (c *context) Fill(m Map) { + for ctx := c; ctx != nil; { + ctx.mx.RLock() + fill(m, ctx.data) + next := ctx.parent + if next == nil { + next = ctx.branchedFrom + } + ctx.mx.RUnlock() + ctx = next + } +} + +func (cm *manager) AsMap(obj interface{}, includeGlobals bool) Map { + return cm.currentContext().asMap(cm, obj, includeGlobals) +} + +func (c *context) AsMap(obj interface{}, includeGlobals bool) Map { + return c.asMap(c.cm, obj, includeGlobals) +} + +func (c *context) asMap(cm *manager, obj interface{}, includeGlobals bool) Map { + result := make(Map, 0) + cl, ok := obj.(Contextual) + if ok { + cl.Fill(result) + } + if c != nil { + c.Fill(result) + } + if includeGlobals { + cm.mxGlobal.RLock() + fill(result, cm.global) + cm.mxGlobal.RUnlock() + } + return result +} + +func fill(m Map, from Map) { + if m != nil { + doFill := func(key string, _value interface{}) { + switch value := _value.(type) { + case map[string]interface{}: + for k, v := range value { + m[k] = v + } + default: + m[key] = value + } + } + + for key, value := range from { + _, alreadyRead := m[key] + if !alreadyRead { + switch v := value.(type) { + case *dynval: + doFill(key, v.fn()) + default: + doFill(key, v) + } + } + } + } +} + +func (cm *manager) currentContext() *context { + id := curGoroutineID() + cm.mxContexts.RLock() + c := cm.contexts[id] + cm.mxContexts.RUnlock() + return c +} diff --git a/vendor/github.com/getlantern/context/context_test.go b/vendor/github.com/getlantern/context/context_test.go new file mode 100644 index 0000000..9fb86f3 --- /dev/null +++ b/vendor/github.com/getlantern/context/context_test.go @@ -0,0 +1,160 @@ +package context + +import ( + "sync" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestStack(t *testing.T) { + cm := NewManager() + _cm := cm.(*manager) + // Put globals first + cm.PutGlobal("a", -1) // This will get overriden in specific contexts + cm.PutGlobal("ga", "i") + cm.PutGlobalDynamic("gb", func() interface{} { return "ii" }) + cm.PutGlobalDynamic("gm", func() interface{} { return map[string]interface{}{"gm3": "iii", "gm4": "iv"} }) + + // Use a Map as a Contextual + var contextual = Map{ + "a": 0, // This will override whatever is in specific contexts + "contextual": "special", + } + + c := cm.Enter() + c.Put("a", 1) + penultimate := cm.Enter(). + Put("b", 2) + c = cm.Enter(). + PutDynamic("c", func() interface{} { return 4 }). + PutIfAbsent("d", 5). + PutIfAbsent("a", 11) + + // Put something in the penultimate context and make sure it doesn't override + // what's set in the ultimate context + penultimate.Put("c", 3) + + var assertMutex sync.Mutex + doAssertContents := func(expected Map, actual Map, scope string) { + assertMutex.Lock() + assert.Equal(t, expected, actual, scope) + assertMutex.Unlock() + } + + assertContents := func(expected Map) { + doAssertContents(expected, cm.AsMap(nil, false), "AsMapwith(nil, false)") + expected["ga"] = "i" + expected["gb"] = "ii" + expected["gm3"] = "iii" + expected["gm4"] = "iv" + _, exists := expected["a"] + if !exists { + expected["a"] = -1 + } + doAssertContents(expected, cm.AsMap(nil, true), "AsMap(nil, true)") + expected["a"] = 0 + expected["contextual"] = "special" + doAssertContents(expected, cm.AsMap(contextual, true), "AsMapWith(contextual, true)") + delete(expected, "ga") + delete(expected, "gb") + delete(expected, "gm3") + delete(expected, "gm4") + doAssertContents(expected, cm.AsMap(contextual, false), "AsMapWith(contextual, false)") + } + + assertContents(Map{ + "a": 1, + "b": 2, + "c": 4, + "d": 5, + }) + + var wg sync.WaitGroup + wg.Add(1) + cm.Go(func() { + defer cm.Enter().Put("e", 6).Exit() + assertContents(Map{ + "a": 1, + "b": 2, + "c": 4, + "d": 5, + "e": 6, + }) + wg.Done() + }) + wg.Wait() + + wg.Add(1) + cm.Go(func() { + // This goroutine doesn't Exit. Still, we shouldn't leak anything. + wg.Done() + }) + wg.Wait() + + assertContents(Map{ + "a": 1, + "b": 2, + "c": 4, + "d": 5, + }) + + c.Exit() + c = _cm.currentContext() + assert.NotNil(t, c) + assertContents(Map{ + "a": 1, + "b": 2, + "c": 3, + }) + + c.Exit() + c = _cm.currentContext() + assert.NotNil(t, c) + assertContents(Map{ + "a": 1, + }) + + // Last exit + c.Exit() + assert.Nil(t, _cm.currentContext()) + assertContents(Map{}) + + // Exit again, just for good measure + c.Exit() + assert.Nil(t, _cm.currentContext()) + assertContents(Map{}) + + // Spawn a goroutine with no existing contexts + wg.Add(1) + cm.Go(func() { + defer cm.Enter().Put("f", 7).Exit() + assertContents(Map{ + "f": 7, + }) + wg.Done() + }) + wg.Wait() + + _cm.mxContexts.Lock() + assert.Empty(t, _cm.contexts, "No contexts should be left") + _cm.mxContexts.Unlock() +} + +func BenchmarkPut(b *testing.B) { + cm := NewManager() + c := cm.Enter() + b.ResetTimer() + for i := 0; i < b.N; i++ { + c.Put("key", "value") + } +} + +func BenchmarkAsMap(b *testing.B) { + cm := NewManager() + cm.Enter().Put("a", 1).Put("b", 2) + b.ResetTimer() + for i := 0; i < b.N; i++ { + cm.AsMap(nil, true) + } +} diff --git a/vendor/github.com/getlantern/context/gotrack.go b/vendor/github.com/getlantern/context/gotrack.go new file mode 100644 index 0000000..7f92346 --- /dev/null +++ b/vendor/github.com/getlantern/context/gotrack.go @@ -0,0 +1,130 @@ +package context + +import ( + "bytes" + "errors" + "fmt" + "runtime" + "strconv" + "sync" +) + +// Sourced https://github.com/bradfitz/http2/blob/dc0c5c000ec33e263612939744d51a3b68b9cece/gotrack.go +var goroutineSpace = []byte("goroutine ") +var littleBuf = sync.Pool{ + New: func() interface{} { + buf := make([]byte, 64) + return &buf + }, +} + +func curGoroutineID() uint64 { + bp := littleBuf.Get().(*[]byte) + defer littleBuf.Put(bp) + b := *bp + b = b[:runtime.Stack(b, false)] + // Parse the 4707 out of "goroutine 4707 [" + b = bytes.TrimPrefix(b, goroutineSpace) + i := bytes.IndexByte(b, ' ') + if i < 0 { + panic(fmt.Sprintf("No space found in %q", b)) + } + b = b[:i] + n, err := parseUintBytes(b, 10, 64) + if err != nil { + panic(fmt.Sprintf("Failed to parse goroutine ID out of %q: %v", b, err)) + } + return n +} + +// parseUintBytes is like strconv.ParseUint, but using a []byte. +func parseUintBytes(s []byte, base int, bitSize int) (n uint64, err error) { + var cutoff, maxVal uint64 + + if bitSize == 0 { + bitSize = int(strconv.IntSize) + } + + s0 := s + switch { + case len(s) < 1: + err = strconv.ErrSyntax + return n, &strconv.NumError{Func: "ParseUint", Num: string(s0), Err: err} + + case 2 <= base && base <= 36: + // valid base; nothing to do + + case base == 0: + // Look for octal, hex prefix. + switch { + case s[0] == '0' && len(s) > 1 && (s[1] == 'x' || s[1] == 'X'): + base = 16 + s = s[2:] + if len(s) < 1 { + err = strconv.ErrSyntax + return n, &strconv.NumError{Func: "ParseUint", Num: string(s0), Err: err} + } + case s[0] == '0': + base = 8 + default: + base = 10 + } + + default: + err = errors.New("invalid base " + strconv.Itoa(base)) + return n, &strconv.NumError{Func: "ParseUint", Num: string(s0), Err: err} + } + + n = 0 + cutoff = cutoff64(base) + maxVal = 1<= base { + n = 0 + err = strconv.ErrSyntax + return n, &strconv.NumError{Func: "ParseUint", Num: string(s0), Err: err} + } + + if n >= cutoff { + // n*base overflows + n = 1<<64 - 1 + err = strconv.ErrRange + return n, &strconv.NumError{Func: "ParseUint", Num: string(s0), Err: err} + } + n *= uint64(base) + + n1 := n + uint64(v) + if n1 < n || n1 > maxVal { + // n+v overflows + n = 1<<64 - 1 + err = strconv.ErrRange + return n, &strconv.NumError{Func: "ParseUint", Num: string(s0), Err: err} + } + n = n1 + } + + return n, nil +} + +// Return the first number n such that n*base >= 1<<64. +func cutoff64(base int) uint64 { + if base < 2 { + return 0 + } + return (1<<64-1)/uint64(base) + 1 +} diff --git a/vendor/github.com/getlantern/errors/errors.go b/vendor/github.com/getlantern/errors/errors.go new file mode 100644 index 0000000..8b2b84f --- /dev/null +++ b/vendor/github.com/getlantern/errors/errors.go @@ -0,0 +1,566 @@ +/* +Package errors defines error types used across Lantern project. + + n, err := Foo() + if err != nil { + return n, errors.New("Unable to do Foo: %v", err) + } + +or + + n, err := Foo() + return n, errors.Wrap(err) + +New() method will create a new error with err as its cause. Wrap will wrap err, +returning nil if err is nil. If err is an error from Go's standard library, +errors will extract details from that error, at least the Go type name and the +return value of err.Error(). + +One can record the operation on which the error occurred using Op(): + + return n, errors.New("Unable to do Foo: %v", err).Op("FooDooer") + +One can also record additional data: + + return n, errors. + New("Unable to do Foo: %v", err). + Op("FooDooer"). + With("mydata", "myvalue"). + With("moredata", 5) + +When used with github.com/getlantern/ops, Error captures its current context +and propagates that data for use in calling layers. + +When used with github.com/getlantern/golog, Error provides stacktraces: + + Hello World + at github.com/getlantern/errors.TestNewWithCause (errors_test.go:999) + at testing.tRunner (testing.go:999) + at runtime.goexit (asm_amd999.s:999) + Caused by: World + at github.com/getlantern/errors.buildCause (errors_test.go:999) + at github.com/getlantern/errors.TestNewWithCause (errors_test.go:999) + at testing.tRunner (testing.go:999) + at runtime.goexit (asm_amd999.s:999) + Caused by: orld + Caused by: ld + at github.com/getlantern/errors.buildSubSubCause (errors_test.go:999) + at github.com/getlantern/errors.buildSubCause (errors_test.go:999) + at github.com/getlantern/errors.buildCause (errors_test.go:999) + at github.com/getlantern/errors.TestNewWithCause (errors_test.go:999) + at testing.tRunner (testing.go:999) + at runtime.goexit (asm_amd999.s:999) + Caused by: d + +It's the caller's responsibility to avoid race conditions accessing the same +error instance from multiple goroutines. +*/ +package errors + +import ( + "bufio" + "bytes" + "crypto/tls" + "crypto/x509" + "encoding/hex" + "encoding/json" + "fmt" + "io" + "net" + "net/http" + "net/textproto" + "net/url" + "os" + "os/exec" + "reflect" + "runtime" + "strconv" + "strings" + "syscall" + "time" + "unicode" + + "github.com/getlantern/context" + "github.com/getlantern/hidden" + "github.com/getlantern/ops" + "github.com/go-stack/stack" +) + +// Error wraps system and application defined errors in unified structure for +// reporting and logging. It's not meant to be created directly. User New(), +// Wrap() and Report() instead. +type Error interface { + error + context.Contextual + + // ErrorClean returns a non-parameterized version of the error whenever + // possible. For example, if the error text is: + // + // unable to dial www.google.com caused by: i/o timeout + // + // ErrorClean might return: + // + // unable to dial %v caused by: %v + // + // This can be useful when performing analytics on the error. + ErrorClean() string + + // MultiLinePrinter implements the interface golog.MultiLine + MultiLinePrinter() func(buf *bytes.Buffer) bool + + // Op attaches a hint of the operation triggers this Error. Many error types + // returned by net and os package have Op pre-filled. + Op(op string) Error + + // With attaches arbitrary field to the error. keys will be normalized as + // underscore_divided_words, so all characters except letters and numbers will + // be replaced with underscores, and all letters will be lowercased. + With(key string, value interface{}) Error + + // RootCause returns the bottom-most cause of this Error. If the Error + // resulted from wrapping a plain error, the wrapped error will be returned as + // the cause. + RootCause() error +} + +type structured struct { + id uint64 + hiddenID string + data context.Map + context context.Map + wrapped error + cause Error + callStack stack.CallStack +} + +// New creates an Error with supplied description and format arguments to the +// description. If any of the arguments is an error, we use that as the cause. +func New(desc string, args ...interface{}) Error { + return NewOffset(1, desc, args...) +} + +// NewOffset is like New but offsets the stack by the given offset. This is +// useful for utilities like golog that may create errors on behalf of others. +func NewOffset(offset int, desc string, args ...interface{}) Error { + var cause error + for _, arg := range args { + err, isError := arg.(error) + if isError { + cause = err + break + } + } + e := buildError(desc, fmt.Sprintf(desc, args...), nil, Wrap(cause)) + e.attachStack(2 + offset) + return e +} + +// Wrap creates an Error based on the information in an error instance. It +// returns nil if the error passed in is nil, so we can simply call +// errors.Wrap(s.l.Close()) regardless there's an error or not. If the error is +// already wrapped, it is returned as is. +func Wrap(err error) Error { + return wrapSkipFrames(err, 1) +} + +// Fill implements the method from the context.Contextual interface. +func (e *structured) Fill(m context.Map) { + if e != nil { + if e.cause != nil { + // Include data from cause, which supercedes context + e.cause.Fill(m) + } + // Include the context, which supercedes the cause + for key, value := range e.context { + m[key] = value + } + // Now include the error's data, which supercedes everything + for key, value := range e.data { + m[key] = value + } + } +} + +func (e *structured) Op(op string) Error { + e.data["error_op"] = op + return e +} + +func (e *structured) With(key string, value interface{}) Error { + parts := strings.FieldsFunc(key, func(c rune) bool { + return !unicode.IsLetter(c) && !unicode.IsNumber(c) + }) + k := strings.ToLower(strings.Join(parts, "_")) + if k == "error" || k == "error_op" { + // Never overwrite these + return e + } + switch actual := value.(type) { + case string, int, bool, time.Time: + e.data[k] = actual + default: + e.data[k] = fmt.Sprint(actual) + } + return e +} + +func (e *structured) RootCause() error { + if e.cause == nil { + if e.wrapped != nil { + return e.wrapped + } + return e + } + return e.cause.RootCause() +} + +func (e *structured) ErrorClean() string { + return e.data["error"].(string) +} + +// Error satisfies the error interface +func (e *structured) Error() string { + return e.data["error_text"].(string) + e.hiddenID +} + +func (e *structured) MultiLinePrinter() func(buf *bytes.Buffer) bool { + first := true + indent := false + err := e + stackPosition := 0 + switchedCause := false + return func(buf *bytes.Buffer) bool { + if indent { + buf.WriteString(" ") + } + if first { + buf.WriteString(e.Error()) + first = false + indent = true + return true + } + if switchedCause { + fmt.Fprintf(buf, "Caused by: %v", err) + if err.callStack != nil && len(err.callStack) > 0 { + switchedCause = false + indent = true + return true + } + if err.cause == nil { + return false + } + err = err.cause.(*structured) + return true + } + if stackPosition < len(err.callStack) { + buf.WriteString("at ") + call := err.callStack[stackPosition] + fmt.Fprintf(buf, "%+n (%s:%d)", call, call, call) + stackPosition++ + } + if stackPosition >= len(err.callStack) { + switch cause := err.cause.(type) { + case *structured: + err = cause + indent = false + stackPosition = 0 + switchedCause = true + default: + return false + } + } + return err != nil + } +} + +func wrapSkipFrames(err error, skip int) Error { + if err == nil { + return nil + } + + // Look for *structureds + if e, ok := err.(*structured); ok { + return e + } + + var cause Error + // Look for hidden *structureds + hiddenIDs, err2 := hidden.Extract(err.Error()) + if err2 == nil && len(hiddenIDs) > 0 { + // Take the first hidden ID as our cause + cause = get(hiddenIDs[0]) + } + + // Create a new *structured + return buildError("", "", err, cause) +} + +func (e *structured) attachStack(skip int) { + call := stack.Caller(skip) + e.callStack = stack.Trace().TrimBelow(call) + e.data["error_location"] = fmt.Sprintf("%+n (%s:%d)", call, call, call) +} + +func buildError(desc string, fullText string, wrapped error, cause Error) *structured { + e := &structured{ + data: make(context.Map), + // We capture the current context to allow it to propagate to higher layers. + context: ops.AsMap(nil, false), + wrapped: wrapped, + cause: cause, + } + e.save() + + errorType := "errors.Error" + if wrapped != nil { + op, goType, wrappedDesc, extra := parseError(wrapped) + if desc == "" { + desc = wrappedDesc + } + e.Op(op) + errorType = goType + if extra != nil { + for key, value := range extra { + e.data[key] = value + } + } + } + + cleanedDesc := hidden.Clean(desc) + e.data["error"] = cleanedDesc + if fullText != "" { + e.data["error_text"] = hidden.Clean(fullText) + } else { + e.data["error_text"] = cleanedDesc + } + e.data["error_type"] = errorType + + return e +} + +func parseError(err error) (op string, goType string, desc string, extra map[string]string) { + extra = make(map[string]string) + + // interfaces + if _, ok := err.(net.Error); ok { + if opError, ok := err.(*net.OpError); ok { + op = opError.Op + if opError.Source != nil { + extra["remote_addr"] = opError.Source.String() + } + if opError.Addr != nil { + extra["local_addr"] = opError.Addr.String() + } + extra["network"] = opError.Net + err = opError.Err + } + switch actual := err.(type) { + case *net.AddrError: + goType = "net.AddrError" + desc = actual.Err + extra["addr"] = actual.Addr + case *net.DNSError: + goType = "net.DNSError" + desc = actual.Err + extra["domain"] = actual.Name + if actual.Server != "" { + extra["dns_server"] = actual.Server + } + case *net.InvalidAddrError: + goType = "net.InvalidAddrError" + desc = actual.Error() + case *net.ParseError: + goType = "net.ParseError" + desc = "invalid " + actual.Type + extra["text_to_parse"] = actual.Text + case net.UnknownNetworkError: + goType = "net.UnknownNetworkError" + desc = "unknown network" + case syscall.Errno: + goType = "syscall.Errno" + desc = actual.Error() + case *url.Error: + goType = "url.Error" + desc = actual.Err.Error() + op = actual.Op + default: + goType = reflect.TypeOf(err).String() + desc = err.Error() + } + return + } + if _, ok := err.(runtime.Error); ok { + desc = err.Error() + switch err.(type) { + case *runtime.TypeAssertionError: + goType = "runtime.TypeAssertionError" + default: + goType = reflect.TypeOf(err).String() + } + return + } + + // structs + switch actual := err.(type) { + case *http.ProtocolError: + desc = actual.ErrorString + if name, ok := httpProtocolErrors[err]; ok { + goType = name + } else { + goType = "http.ProtocolError" + } + case url.EscapeError, *url.EscapeError: + goType = "url.EscapeError" + desc = "invalid URL escape" + case url.InvalidHostError, *url.InvalidHostError: + goType = "url.InvalidHostError" + desc = "invalid character in host name" + case *textproto.Error: + goType = "textproto.Error" + desc = actual.Error() + case textproto.ProtocolError, *textproto.ProtocolError: + goType = "textproto.ProtocolError" + desc = actual.Error() + + case tls.RecordHeaderError: + goType = "tls.RecordHeaderError" + desc = actual.Msg + extra["header"] = hex.EncodeToString(actual.RecordHeader[:]) + case x509.CertificateInvalidError: + goType = "x509.CertificateInvalidError" + desc = actual.Error() + case x509.ConstraintViolationError: + goType = "x509.ConstraintViolationError" + desc = actual.Error() + case x509.HostnameError: + goType = "x509.HostnameError" + desc = actual.Error() + extra["host"] = actual.Host + case x509.InsecureAlgorithmError: + goType = "x509.InsecureAlgorithmError" + desc = actual.Error() + case x509.SystemRootsError: + goType = "x509.SystemRootsError" + desc = actual.Error() + case x509.UnhandledCriticalExtension: + goType = "x509.UnhandledCriticalExtension" + desc = actual.Error() + case x509.UnknownAuthorityError: + goType = "x509.UnknownAuthorityError" + desc = actual.Error() + case hex.InvalidByteError: + goType = "hex.InvalidByteError" + desc = "invalid byte" + case *json.InvalidUTF8Error: + goType = "json.InvalidUTF8Error" + desc = "invalid UTF-8 in string" + case *json.InvalidUnmarshalError: + goType = "json.InvalidUnmarshalError" + desc = actual.Error() + case *json.MarshalerError: + goType = "json.MarshalerError" + desc = actual.Error() + case *json.SyntaxError: + goType = "json.SyntaxError" + desc = actual.Error() + case *json.UnmarshalFieldError: + goType = "json.UnmarshalFieldError" + desc = actual.Error() + case *json.UnmarshalTypeError: + goType = "json.UnmarshalTypeError" + desc = actual.Error() + case *json.UnsupportedTypeError: + goType = "json.UnsupportedTypeError" + desc = actual.Error() + case *json.UnsupportedValueError: + goType = "json.UnsupportedValueError" + desc = actual.Error() + + case *os.LinkError: + goType = "os.LinkError" + desc = actual.Error() + case *os.PathError: + goType = "os.PathError" + op = actual.Op + desc = actual.Err.Error() + case *os.SyscallError: + goType = "os.SyscallError" + op = actual.Syscall + desc = actual.Err.Error() + case *exec.Error: + goType = "exec.Error" + desc = actual.Err.Error() + case *exec.ExitError: + goType = "exec.ExitError" + desc = actual.Error() + // TODO: limit the length + extra["stderr"] = string(actual.Stderr) + case *strconv.NumError: + goType = "strconv.NumError" + desc = actual.Err.Error() + extra["function"] = actual.Func + case *time.ParseError: + goType = "time.ParseError" + desc = actual.Message + default: + desc = err.Error() + if t, ok := miscErrors[err]; ok { + goType = t + return + } + goType = reflect.TypeOf(err).String() + } + return +} + +var httpProtocolErrors = map[error]string{ + http.ErrHeaderTooLong: "http.ErrHeaderTooLong", + http.ErrShortBody: "http.ErrShortBody", + http.ErrNotSupported: "http.ErrNotSupported", + http.ErrUnexpectedTrailer: "http.ErrUnexpectedTrailer", + http.ErrMissingContentLength: "http.ErrMissingContentLength", + http.ErrNotMultipart: "http.ErrNotMultipart", + http.ErrMissingBoundary: "http.ErrMissingBoundary", +} + +var miscErrors = map[error]string{ + bufio.ErrInvalidUnreadByte: "bufio.ErrInvalidUnreadByte", + bufio.ErrInvalidUnreadRune: "bufio.ErrInvalidUnreadRune", + bufio.ErrBufferFull: "bufio.ErrBufferFull", + bufio.ErrNegativeCount: "bufio.ErrNegativeCount", + bufio.ErrTooLong: "bufio.ErrTooLong", + bufio.ErrNegativeAdvance: "bufio.ErrNegativeAdvance", + bufio.ErrAdvanceTooFar: "bufio.ErrAdvanceTooFar", + bufio.ErrFinalToken: "bufio.ErrFinalToken", + + http.ErrWriteAfterFlush: "http.ErrWriteAfterFlush", + http.ErrBodyNotAllowed: "http.ErrBodyNotAllowed", + http.ErrHijacked: "http.ErrHijacked", + http.ErrContentLength: "http.ErrContentLength", + http.ErrBodyReadAfterClose: "http.ErrBodyReadAfterClose", + http.ErrHandlerTimeout: "http.ErrHandlerTimeout", + http.ErrLineTooLong: "http.ErrLineTooLong", + http.ErrMissingFile: "http.ErrMissingFile", + http.ErrNoCookie: "http.ErrNoCookie", + http.ErrNoLocation: "http.ErrNoLocation", + http.ErrSkipAltProtocol: "http.ErrSkipAltProtocol", + + io.EOF: "io.EOF", + io.ErrClosedPipe: "io.ErrClosedPipe", + io.ErrNoProgress: "io.ErrNoProgress", + io.ErrShortBuffer: "io.ErrShortBuffer", + io.ErrShortWrite: "io.ErrShortWrite", + io.ErrUnexpectedEOF: "io.ErrUnexpectedEOF", + + os.ErrInvalid: "os.ErrInvalid", + os.ErrPermission: "os.ErrPermission", + os.ErrExist: "os.ErrExist", + os.ErrNotExist: "os.ErrNotExist", + + exec.ErrNotFound: "exec.ErrNotFound", + + x509.ErrUnsupportedAlgorithm: "x509.ErrUnsupportedAlgorithm", + x509.IncorrectPasswordError: "x509.IncorrectPasswordError", + + hex.ErrLength: "hex.ErrLength", +} diff --git a/vendor/github.com/getlantern/errors/errors_test.go b/vendor/github.com/getlantern/errors/errors_test.go new file mode 100644 index 0000000..7c4887a --- /dev/null +++ b/vendor/github.com/getlantern/errors/errors_test.go @@ -0,0 +1,142 @@ +package errors + +import ( + "bytes" + "fmt" + "regexp" + "testing" + + "github.com/getlantern/context" + "github.com/getlantern/hidden" + "github.com/getlantern/ops" + "github.com/stretchr/testify/assert" +) + +var ( + replaceNumbers = regexp.MustCompile("[0-9]+") +) + +func TestFull(t *testing.T) { + var firstErr Error + + // Iterate past the size of the hidden buffer + for i := 0; i < len(hiddenErrors)*2; i++ { + op := ops.Begin("op1").Set("ca", 100).Set("cd", 100) + e := New("Hello %v", "There").Op("My Op").With("DaTa_1", 1) + op.End() + if firstErr == nil { + firstErr = e + } + assert.Equal(t, "Hello There", e.Error()[:11]) + op = ops.Begin("op2").Set("ca", 200).Set("cb", 200).Set("cc", 200) + e3 := Wrap(fmt.Errorf("I'm wrapping your text: %v", e)).Op("outer op").With("dATA+1", i).With("cb", 300) + op.End() + assert.Equal(t, e, e3.(*structured).cause, "Wrapping a regular error should have extracted the contained *Error") + m := make(context.Map) + e3.Fill(m) + assert.Equal(t, i, m["data_1"], "Error's data should dominate all") + assert.Equal(t, 200, m["ca"], "Error's context should dominate cause") + assert.Equal(t, 300, m["cb"], "Error's data should dominate its context") + assert.Equal(t, 200, m["cc"], "Error's context should come through") + assert.Equal(t, 100, m["cd"], "Cause's context should come through") + assert.Equal(t, "My Op", e.(*structured).data["error_op"], "Op should be available from cause") + + for _, call := range e3.(*structured).callStack { + t.Logf("at %v", call) + } + } + + e3 := Wrap(fmt.Errorf("I'm wrapping your text: %v", firstErr)).With("a", 2) + assert.Nil(t, e3.(*structured).cause, "Wrapping an *Error that's no longer buffered should have yielded no cause") +} + +func TestNewWithCause(t *testing.T) { + cause := buildCause() + outer := New("Hello %v", cause) + assert.Equal(t, "Hello World", hidden.Clean(outer.Error())) + assert.Equal(t, "Hello %v", outer.(*structured).ErrorClean()) + assert.Equal(t, + "github.com/getlantern/errors.TestNewWithCause (errors_test.go:999)", + replaceNumbers.ReplaceAllString(outer.(*structured).data["error_location"].(string), "999")) + assert.Equal(t, cause, outer.(*structured).cause) + + // Make sure that stacktrace prints out okay + buf := &bytes.Buffer{} + print := outer.MultiLinePrinter() + for { + more := print(buf) + buf.WriteByte('\n') + if !more { + break + } + } + expected := `Hello World + at github.com/getlantern/errors.TestNewWithCause (errors_test.go:999) + at testing.tRunner (testing.go:999) + at runtime.goexit (asm_amd999.s:999) +Caused by: World + at github.com/getlantern/errors.buildCause (errors_test.go:999) + at github.com/getlantern/errors.TestNewWithCause (errors_test.go:999) + at testing.tRunner (testing.go:999) + at runtime.goexit (asm_amd999.s:999) +Caused by: orld +Caused by: ld + at github.com/getlantern/errors.buildSubSubCause (errors_test.go:999) + at github.com/getlantern/errors.buildSubCause (errors_test.go:999) + at github.com/getlantern/errors.buildCause (errors_test.go:999) + at github.com/getlantern/errors.TestNewWithCause (errors_test.go:999) + at testing.tRunner (testing.go:999) + at runtime.goexit (asm_amd999.s:999) +Caused by: d +` + + assert.Equal(t, expected, replaceNumbers.ReplaceAllString(hidden.Clean(buf.String()), "999")) + assert.Equal(t, buildSubSubSubCause(), outer.RootCause()) +} + +func buildCause() Error { + return New("W%v", buildSubCause()) +} + +func buildSubCause() error { + return fmt.Errorf("or%v", buildSubSubCause()) +} + +func buildSubSubCause() error { + return New("l%v", buildSubSubSubCause()) +} + +func buildSubSubSubCause() error { + return fmt.Errorf("d") +} + +func TestWrapNil(t *testing.T) { + assert.Nil(t, doWrapNil()) +} + +func doWrapNil() error { + return Wrap(nil) +} + +func TestHiddenWithCause(t *testing.T) { + e1 := fmt.Errorf("I failed %v", "dude") + e2 := New("I wrap: %v", e1) + e3 := fmt.Errorf("Hiding %v", e2) + // clear hidden buffer + hiddenErrors = make([]*structured, 100) + e4 := Wrap(e3) + e5 := New("I'm really outer: %v", e4) + + buf := &bytes.Buffer{} + print := e5.MultiLinePrinter() + for { + more := print(buf) + buf.WriteByte('\n') + if !more { + break + } + } + fmt.Println(buf.String()) + // We're not asserting the output because we're just making sure that printing + // doesn't panic. If we get to this point without panicking, we're happy. +} diff --git a/vendor/github.com/getlantern/errors/hide.go b/vendor/github.com/getlantern/errors/hide.go new file mode 100644 index 0000000..f10d863 --- /dev/null +++ b/vendor/github.com/getlantern/errors/hide.go @@ -0,0 +1,50 @@ +package errors + +import ( + "encoding/binary" + "sync" + + "github.com/getlantern/hidden" +) + +var ( + hiddenErrors = make([]*structured, 100) + nextID = uint64(0) + hiddenMutex sync.RWMutex +) + +// This trick saves the error to a ring buffer and embeds a non-printing +// hiddenID in the error's description, so that if the errors is later wrapped +// by a standard error using something like +// fmt.Errorf("An error occurred: %v", thisError), we can subsequently extract +// the error simply using the hiddenID in the string. +func (e *structured) save() { + hiddenMutex.Lock() + b := make([]byte, 8) + binary.BigEndian.PutUint64(b, nextID) + e.id = nextID + e.hiddenID = hidden.ToString(b) + hiddenErrors[idxForID(nextID)] = e + nextID++ + hiddenMutex.Unlock() +} + +func get(hiddenID []byte) Error { + if len(hiddenID) != 8 { + return nil + } + id := binary.BigEndian.Uint64(hiddenID) + hiddenMutex.RLock() + err := hiddenErrors[idxForID(id)] + hiddenMutex.RUnlock() + if err != nil && err.id == id { + // Found it! + return err + } + // buffer has rolled over + return nil +} + +func idxForID(id uint64) int { + return int(id % uint64(len(hiddenErrors))) +} diff --git a/vendor/github.com/getlantern/golog/LICENSE b/vendor/github.com/getlantern/golog/LICENSE new file mode 100644 index 0000000..3ee0162 --- /dev/null +++ b/vendor/github.com/getlantern/golog/LICENSE @@ -0,0 +1,202 @@ + + 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: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) 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 + + (d) 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 2014 Brave New Software Project, Inc. + + 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/vendor/github.com/getlantern/golog/README.md b/vendor/github.com/getlantern/golog/README.md new file mode 100644 index 0000000..88fe677 --- /dev/null +++ b/vendor/github.com/getlantern/golog/README.md @@ -0,0 +1,6 @@ +golog [![Travis CI Status](https://travis-ci.org/getlantern/golog.svg?branch=master)](https://travis-ci.org/getlantern/golog) [![Coverage Status](https://coveralls.io/repos/getlantern/golog/badge.png)](https://coveralls.io/r/getlantern/golog) [![GoDoc](https://godoc.org/github.com/getlantern/golog?status.png)](http://godoc.org/github.com/getlantern/golog) +========== +Provides logging used in many getlantern components. + +[GoDoc](https://godoc.org/github.com/getlantern/golog) + diff --git a/vendor/github.com/getlantern/golog/golog.go b/vendor/github.com/getlantern/golog/golog.go new file mode 100644 index 0000000..143e904 --- /dev/null +++ b/vendor/github.com/getlantern/golog/golog.go @@ -0,0 +1,458 @@ +// Package golog implements logging functions that log errors to stderr and +// debug messages to stdout. Trace logging is also supported. +// Trace logs go to stdout as well, but they are only written if the program +// is run with environment variable "TRACE=true". +// A stack dump will be printed after the message if "PRINT_STACK=true". +package golog + +import ( + "bufio" + "bytes" + "fmt" + "io" + "io/ioutil" + "log" + "os" + "path/filepath" + "runtime" + "sort" + "strconv" + "strings" + "sync" + "sync/atomic" + + "github.com/getlantern/errors" + "github.com/getlantern/hidden" + "github.com/getlantern/ops" + "github.com/oxtoacart/bpool" +) + +const ( + // ERROR is an error Severity + ERROR = 500 + + // FATAL is an error Severity + FATAL = 600 +) + +var ( + outs atomic.Value + reporters []ErrorReporter + reportersMutex sync.RWMutex + + bufferPool = bpool.NewBufferPool(200) + + onFatal atomic.Value +) + +// Severity is a level of error (higher values are more severe) +type Severity int + +func (s Severity) String() string { + switch s { + case ERROR: + return "ERROR" + case FATAL: + return "FATAL" + default: + return "UNKNOWN" + } +} + +func init() { + DefaultOnFatal() + ResetOutputs() +} + +func SetOutputs(errorOut io.Writer, debugOut io.Writer) { + outs.Store(&outputs{ + ErrorOut: errorOut, + DebugOut: debugOut, + }) +} + +func ResetOutputs() { + SetOutputs(os.Stderr, os.Stdout) +} + +func GetOutputs() *outputs { + return outs.Load().(*outputs) +} + +// RegisterReporter registers the given ErrorReporter. All logged Errors are +// sent to this reporter. +func RegisterReporter(reporter ErrorReporter) { + reportersMutex.Lock() + reporters = append(reporters, reporter) + reportersMutex.Unlock() +} + +// OnFatal configures golog to call the given function on any FATAL error. By +// default, golog calls os.Exit(1) on any FATAL error. +func OnFatal(fn func(err error)) { + onFatal.Store(fn) +} + +// DefaultOnFatal enables the default behavior for OnFatal +func DefaultOnFatal() { + onFatal.Store(func(err error) { + os.Exit(1) + }) +} + +type outputs struct { + ErrorOut io.Writer + DebugOut io.Writer +} + +// MultiLine is an interface for arguments that support multi-line output. +type MultiLine interface { + // MultiLinePrinter returns a function that can be used to print the + // multi-line output. The returned function writes one line to the buffer and + // returns true if there are more lines to write. This function does not need + // to take care of trailing carriage returns, golog handles that + // automatically. + MultiLinePrinter() func(buf *bytes.Buffer) bool +} + +// ErrorReporter is a function to which the logger will report errors. +// It the given error and corresponding message along with associated ops +// context. This should return quickly as it executes on the critical code +// path. The recommended approach is to buffer as much as possible and discard +// new reports if the buffer becomes saturated. +type ErrorReporter func(err error, linePrefix string, severity Severity, ctx map[string]interface{}) + +type Logger interface { + // Debug logs to stdout + Debug(arg interface{}) + // Debugf logs to stdout + Debugf(message string, args ...interface{}) + + // Error logs to stderr + Error(arg interface{}) error + // Errorf logs to stderr. It returns the first argument that's an error, or + // a new error built using fmt.Errorf if none of the arguments are errors. + Errorf(message string, args ...interface{}) error + + // Fatal logs to stderr and then exits with status 1 + Fatal(arg interface{}) + // Fatalf logs to stderr and then exits with status 1 + Fatalf(message string, args ...interface{}) + + // Trace logs to stderr only if TRACE=true + Trace(arg interface{}) + // Tracef logs to stderr only if TRACE=true + Tracef(message string, args ...interface{}) + + // TraceOut provides access to an io.Writer to which trace information can + // be streamed. If running with environment variable "TRACE=true", TraceOut + // will point to os.Stderr, otherwise it will point to a ioutil.Discared. + // Each line of trace information will be prefixed with this Logger's + // prefix. + TraceOut() io.Writer + + // IsTraceEnabled() indicates whether or not tracing is enabled for this + // logger. + IsTraceEnabled() bool + + // AsStdLogger returns an standard logger + AsStdLogger() *log.Logger +} + +func LoggerFor(prefix string) Logger { + l := &logger{ + prefix: prefix + ": ", + pc: make([]uintptr, 10), + } + + trace := os.Getenv("TRACE") + l.traceOn, _ = strconv.ParseBool(trace) + if !l.traceOn { + prefixes := strings.Split(trace, ",") + for _, p := range prefixes { + if prefix == strings.Trim(p, " ") { + l.traceOn = true + break + } + } + } + if l.traceOn { + l.traceOut = l.newTraceWriter() + } else { + l.traceOut = ioutil.Discard + } + + printStack := os.Getenv("PRINT_STACK") + l.printStack, _ = strconv.ParseBool(printStack) + + return l +} + +type logger struct { + prefix string + traceOn bool + traceOut io.Writer + printStack bool + outs atomic.Value + pc []uintptr + funcForPc *runtime.Func +} + +// attaches the file and line number corresponding to +// the log message +func (l *logger) linePrefix(skipFrames int) string { + runtime.Callers(skipFrames, l.pc) + funcForPc := runtime.FuncForPC(l.pc[0]) + file, line := funcForPc.FileLine(l.pc[0] - 1) + return fmt.Sprintf("%s%s:%d ", l.prefix, filepath.Base(file), line) +} + +func (l *logger) print(out io.Writer, skipFrames int, severity string, arg interface{}) string { + buf := bufferPool.Get() + defer bufferPool.Put(buf) + + linePrefix := l.linePrefix(skipFrames) + writeHeader := func() { + buf.WriteString(severity) + buf.WriteString(" ") + buf.WriteString(linePrefix) + } + if arg != nil { + ml, isMultiline := arg.(MultiLine) + if !isMultiline { + writeHeader() + fmt.Fprintf(buf, "%v", arg) + printContext(buf, arg) + buf.WriteByte('\n') + } else { + mlp := ml.MultiLinePrinter() + first := true + for { + writeHeader() + more := mlp(buf) + if first { + printContext(buf, arg) + first = false + } + buf.WriteByte('\n') + if !more { + break + } + } + } + } + b := []byte(hidden.Clean(buf.String())) + _, err := out.Write(b) + if err != nil { + errorOnLogging(err) + } + if l.printStack { + l.doPrintStack() + } + + return linePrefix +} + +func (l *logger) printf(out io.Writer, skipFrames int, severity string, err error, message string, args ...interface{}) string { + buf := bufferPool.Get() + defer bufferPool.Put(buf) + + linePrefix := l.linePrefix(skipFrames) + buf.WriteString(severity) + buf.WriteString(" ") + buf.WriteString(linePrefix) + fmt.Fprintf(buf, message, args...) + printContext(buf, err) + buf.WriteByte('\n') + b := []byte(hidden.Clean(buf.String())) + _, err2 := out.Write(b) + if err2 != nil { + errorOnLogging(err) + } + if l.printStack { + l.doPrintStack() + } + return linePrefix +} + +func (l *logger) Debug(arg interface{}) { + l.print(GetOutputs().DebugOut, 4, "DEBUG", arg) +} + +func (l *logger) Debugf(message string, args ...interface{}) { + l.printf(GetOutputs().DebugOut, 4, "DEBUG", nil, message, args...) +} + +func (l *logger) Error(arg interface{}) error { + return l.errorSkipFrames(arg, 1, ERROR) +} + +func (l *logger) Errorf(message string, args ...interface{}) error { + return l.errorSkipFrames(errors.NewOffset(1, message, args...), 1, ERROR) +} + +func (l *logger) Fatal(arg interface{}) { + fatal(l.errorSkipFrames(arg, 1, FATAL)) +} + +func (l *logger) Fatalf(message string, args ...interface{}) { + fatal(l.errorSkipFrames(errors.NewOffset(1, message, args...), 1, FATAL)) +} + +func fatal(err error) { + fn := onFatal.Load().(func(err error)) + fn(err) +} + +func (l *logger) errorSkipFrames(arg interface{}, skipFrames int, severity Severity) error { + var err error + switch e := arg.(type) { + case error: + err = e + default: + err = fmt.Errorf("%v", e) + } + linePrefix := l.print(GetOutputs().ErrorOut, skipFrames+4, severity.String(), err) + return report(err, linePrefix, severity) +} + +func (l *logger) Trace(arg interface{}) { + if l.traceOn { + l.print(GetOutputs().DebugOut, 4, "TRACE", arg) + } +} + +func (l *logger) Tracef(message string, args ...interface{}) { + if l.traceOn { + l.printf(GetOutputs().DebugOut, 4, "TRACE", nil, message, args...) + } +} + +func (l *logger) TraceOut() io.Writer { + return l.traceOut +} + +func (l *logger) IsTraceEnabled() bool { + return l.traceOn +} + +func (l *logger) newTraceWriter() io.Writer { + pr, pw := io.Pipe() + br := bufio.NewReader(pr) + + if !l.traceOn { + return pw + } + go func() { + defer func() { + if err := pr.Close(); err != nil { + errorOnLogging(err) + } + }() + defer func() { + if err := pw.Close(); err != nil { + errorOnLogging(err) + } + }() + + for { + line, err := br.ReadString('\n') + if err == nil { + // Log the line (minus the trailing newline) + l.print(GetOutputs().DebugOut, 6, "TRACE", line[:len(line)-1]) + } else { + l.printf(GetOutputs().DebugOut, 6, "TRACE", nil, "TraceWriter closed due to unexpected error: %v", err) + return + } + } + }() + + return pw +} + +type errorWriter struct { + l *logger +} + +// Write implements method of io.Writer, due to different call depth, +// it will not log correct file and line prefix +func (w *errorWriter) Write(p []byte) (n int, err error) { + s := string(p) + if s[len(s)-1] == '\n' { + s = s[:len(s)-1] + } + w.l.print(GetOutputs().ErrorOut, 6, "ERROR", s) + return len(p), nil +} + +func (l *logger) AsStdLogger() *log.Logger { + return log.New(&errorWriter{l}, "", 0) +} + +func (l *logger) doPrintStack() { + var b []byte + buf := bytes.NewBuffer(b) + for _, pc := range l.pc { + funcForPc := runtime.FuncForPC(pc) + if funcForPc == nil { + break + } + name := funcForPc.Name() + if strings.HasPrefix(name, "runtime.") { + break + } + file, line := funcForPc.FileLine(pc) + fmt.Fprintf(buf, "\t%s\t%s: %d\n", name, file, line) + } + if _, err := buf.WriteTo(os.Stderr); err != nil { + errorOnLogging(err) + } +} + +func errorOnLogging(err error) { + fmt.Fprintf(os.Stderr, "Unable to log: %v\n", err) +} + +func printContext(buf *bytes.Buffer, err interface{}) { + // Note - we don't include globals when printing in order to avoid polluting the text log + values := ops.AsMap(err, false) + if len(values) == 0 { + return + } + buf.WriteString(" [") + var keys []string + for key := range values { + keys = append(keys, key) + } + sort.Strings(keys) + for i, key := range keys { + value := values[key] + if i > 0 { + buf.WriteString(" ") + } + buf.WriteString(key) + buf.WriteString("=") + fmt.Fprintf(buf, "%v", value) + } + buf.WriteByte(']') +} + +func report(err error, linePrefix string, severity Severity) error { + var reportersCopy []ErrorReporter + reportersMutex.RLock() + if len(reporters) > 0 { + reportersCopy = make([]ErrorReporter, len(reporters)) + copy(reportersCopy, reporters) + } + reportersMutex.RUnlock() + + if len(reportersCopy) > 0 { + ctx := ops.AsMap(err, true) + ctx["severity"] = severity.String() + for _, reporter := range reportersCopy { + // We include globals when reporting + reporter(err, linePrefix, severity, ctx) + } + } + return err +} diff --git a/vendor/github.com/getlantern/golog/golog_test.go b/vendor/github.com/getlantern/golog/golog_test.go new file mode 100644 index 0000000..bce36e8 --- /dev/null +++ b/vendor/github.com/getlantern/golog/golog_test.go @@ -0,0 +1,224 @@ +package golog + +import ( + "bytes" + "io" + "io/ioutil" + "os" + "regexp" + "strings" + "sync" + "testing" + "time" + + "github.com/getlantern/errors" + "github.com/getlantern/ops" + + "github.com/stretchr/testify/assert" +) + +var ( + expectedLog = "SEVERITY myprefix: golog_test.go:999 Hello world\nSEVERITY myprefix: golog_test.go:999 Hello true [cvarA=a cvarB=b op=name root_op=name]\n" + expectedErrorLog = `ERROR myprefix: golog_test.go:999 Hello world [cvarC=c cvarD=d error=Hello %v error_location=github.com/getlantern/golog.TestError (golog_test.go:999) error_text=Hello world error_type=errors.Error op=name root_op=name] +ERROR myprefix: golog_test.go:999 at github.com/getlantern/golog.TestError (golog_test.go:999) +ERROR myprefix: golog_test.go:999 at testing.tRunner (testing.go:999) +ERROR myprefix: golog_test.go:999 at runtime.goexit (asm_amd999.s:999) +ERROR myprefix: golog_test.go:999 Caused by: world +ERROR myprefix: golog_test.go:999 at github.com/getlantern/golog.errorReturner (golog_test.go:999) +ERROR myprefix: golog_test.go:999 at github.com/getlantern/golog.TestError (golog_test.go:999) +ERROR myprefix: golog_test.go:999 at testing.tRunner (testing.go:999) +ERROR myprefix: golog_test.go:999 at runtime.goexit (asm_amd999.s:999) +ERROR myprefix: golog_test.go:999 Hello true [cvarA=a cvarB=b cvarC=c error=%v %v error_location=github.com/getlantern/golog.TestError (golog_test.go:999) error_text=Hello true error_type=errors.Error op=name999 root_op=name999] +ERROR myprefix: golog_test.go:999 at github.com/getlantern/golog.TestError (golog_test.go:999) +ERROR myprefix: golog_test.go:999 at testing.tRunner (testing.go:999) +ERROR myprefix: golog_test.go:999 at runtime.goexit (asm_amd999.s:999) +ERROR myprefix: golog_test.go:999 Caused by: Hello +ERROR myprefix: golog_test.go:999 at github.com/getlantern/golog.TestError (golog_test.go:999) +ERROR myprefix: golog_test.go:999 at testing.tRunner (testing.go:999) +ERROR myprefix: golog_test.go:999 at runtime.goexit (asm_amd999.s:999) +` + expectedTraceLog = "TRACE myprefix: golog_test.go:999 Hello world\nTRACE myprefix: golog_test.go:999 Hello true\nTRACE myprefix: golog_test.go:999 Gravy\nTRACE myprefix: golog_test.go:999 TraceWriter closed due to unexpected error: EOF\n" + expectedStdLog = expectedLog +) + +var ( + replaceNumbers = regexp.MustCompile("[0-9]+") +) + +func init() { + ops.SetGlobal("global", "shouldn't show up") +} + +func expected(severity string, log string) string { + return strings.Replace(log, "SEVERITY", severity, -1) +} + +func normalized(log string) string { + return replaceNumbers.ReplaceAllString(log, "999") +} + +func TestReport(t *testing.T) { + SetOutputs(ioutil.Discard, ioutil.Discard) + OnFatal(func(err error) { + // ignore (prevents test from exiting) + }) + + errors := 0 + fatals := 0 + RegisterReporter(func(err error, linePrefix string, severity Severity, ctx map[string]interface{}) { + switch severity { + case ERROR: + errors++ + case FATAL: + fatals++ + } + }) + l := LoggerFor("reporting") + l.Error("Some error") + l.Fatal("Fatal error") + assert.Equal(t, 1, errors) + assert.Equal(t, 1, fatals) +} + +func TestDebug(t *testing.T) { + out := newBuffer() + SetOutputs(ioutil.Discard, out) + l := LoggerFor("myprefix") + l.Debug("Hello world") + defer ops.Begin("name").Set("cvarA", "a").Set("cvarB", "b").End() + l.Debugf("Hello %v", true) + assert.Equal(t, expected("DEBUG", expectedLog), out.String()) +} + +func TestError(t *testing.T) { + out := newBuffer() + SetOutputs(out, ioutil.Discard) + l := LoggerFor("myprefix") + ctx := ops.Begin("name").Set("cvarC", "c") + err := errorReturner() + err1 := errors.New("Hello %v", err) + err2 := errors.New("Hello") + ctx.End() + l.Error(err1) + defer ops.Begin("name2").Set("cvarA", "a").Set("cvarB", "b").End() + l.Errorf("%v %v", err2, true) + t.Log(out.String()) + assert.Equal(t, expectedErrorLog, out.String()) +} + +func errorReturner() error { + defer ops.Begin("name").Set("cvarD", "d").End() + return errors.New("world") +} + +func TestTraceEnabled(t *testing.T) { + originalTrace := os.Getenv("TRACE") + err := os.Setenv("TRACE", "true") + if err != nil { + t.Fatalf("Unable to set trace to true") + } + defer func() { + if err := os.Setenv("TRACE", originalTrace); err != nil { + t.Fatalf("Unable to set TRACE environment variable: %v", err) + } + }() + + out := newBuffer() + SetOutputs(ioutil.Discard, out) + l := LoggerFor("myprefix") + l.Trace("Hello world") + l.Tracef("Hello %v", true) + tw := l.TraceOut() + if _, err := tw.Write([]byte("Gravy\n")); err != nil { + t.Fatalf("Unable to write: %v", err) + } + if err := tw.(io.Closer).Close(); err != nil { + t.Fatalf("Unable to close: %v", err) + } + + // Give trace writer a moment to catch up + time.Sleep(50 * time.Millisecond) + assert.Regexp(t, expected("TRACE", expectedTraceLog), out.String()) +} + +func TestTraceDisabled(t *testing.T) { + originalTrace := os.Getenv("TRACE") + err := os.Setenv("TRACE", "false") + if err != nil { + t.Fatalf("Unable to set trace to false") + } + defer func() { + if err := os.Setenv("TRACE", originalTrace); err != nil { + t.Fatalf("Unable to set TRACE environment variable: %v", err) + } + }() + + out := newBuffer() + SetOutputs(ioutil.Discard, out) + l := LoggerFor("myprefix") + l.Trace("Hello world") + l.Tracef("Hello %v", true) + if _, err := l.TraceOut().Write([]byte("Gravy\n")); err != nil { + t.Fatalf("Unable to write: %v", err) + } + + // Give trace writer a moment to catch up + time.Sleep(50 * time.Millisecond) + + assert.Equal(t, "", out.String(), "Nothing should have been logged") +} + +func TestAsStdLogger(t *testing.T) { + out := newBuffer() + SetOutputs(out, ioutil.Discard) + l := LoggerFor("myprefix") + stdlog := l.AsStdLogger() + stdlog.Print("Hello world") + defer ops.Begin("name").Set("cvarA", "a").Set("cvarB", "b").End() + stdlog.Printf("Hello %v", true) + assert.Equal(t, expected("ERROR", expectedStdLog), out.String()) +} + +// TODO: TraceWriter appears to have been broken since we added line numbers +// func TestTraceWriter(t *testing.T) { +// originalTrace := os.Getenv("TRACE") +// err := os.Setenv("TRACE", "true") +// if err != nil { +// t.Fatalf("Unable to set trace to true") +// } +// defer func() { +// if err := os.Setenv("TRACE", originalTrace); err != nil { +// t.Fatalf("Unable to set TRACE environment variable: %v", err) +// } +// }() +// +// out := newBuffer() +// SetOutputs(ioutil.Discard, out) +// l := LoggerFor("myprefix") +// trace := l.TraceOut() +// trace.Write([]byte("Hello world\n")) +// defer ops.Begin().Set("cvarA", "a").Set("cvarB", "b").End() +// trace.Write([]byte("Hello true\n")) +// assert.Equal(t, expected("TRACE", expectedStdLog), out.String()) +// } + +func newBuffer() *synchronizedbuffer { + return &synchronizedbuffer{orig: &bytes.Buffer{}} +} + +type synchronizedbuffer struct { + orig *bytes.Buffer + mutex sync.RWMutex +} + +func (buf *synchronizedbuffer) Write(p []byte) (int, error) { + buf.mutex.Lock() + defer buf.mutex.Unlock() + return buf.orig.Write(p) +} + +func (buf *synchronizedbuffer) String() string { + buf.mutex.RLock() + defer buf.mutex.RUnlock() + return normalized(buf.orig.String()) +} diff --git a/vendor/github.com/getlantern/hex/hex.go b/vendor/github.com/getlantern/hex/hex.go new file mode 100644 index 0000000..d1270e9 --- /dev/null +++ b/vendor/github.com/getlantern/hex/hex.go @@ -0,0 +1,107 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package hex implements hexadecimal encoding and decoding. It's taken almost +// verbatim from golang/encoding/hex, however it allows using a different set +// of encoding characters than the standard 0-F. +package hex + +import ( + "errors" + "fmt" +) + +// DefaultEncoding behaves just like golang/encoding/hex. +var DefaultEncoding = NewEncoding("0123456789abcdef") + +// An Encoding that uses a specific table of encoding characters. +type Encoding struct { + hextable string +} + +// NewEncoding constructs an Encoding using the given hextable. +func NewEncoding(hextable string) *Encoding { + return &Encoding{hextable} +} + +// EncodedLen returns the length of an encoding of n source bytes. +func EncodedLen(n int) int { return n * 2 } + +// Encode encodes src into EncodedLen(len(src)) +// bytes of dst. As a convenience, it returns the number +// of bytes written to dst, but this value is always EncodedLen(len(src)). +// Encode implements hexadecimal encoding. +func (e *Encoding) Encode(dst, src []byte) int { + for i, v := range src { + dst[i*2] = e.hextable[v>>4] + dst[i*2+1] = e.hextable[v&0x0f] + } + + return len(src) * 2 +} + +// ErrLength results from decoding an odd length slice. +var ErrLength = errors.New("encoding/hex: odd length hex string") + +// InvalidByteError values describe errors resulting from an invalid byte in a hex string. +type InvalidByteError byte + +func (e InvalidByteError) Error() string { + return fmt.Sprintf("encoding/hex: invalid byte: %#U", rune(e)) +} + +func DecodedLen(x int) int { return x / 2 } + +// Decode decodes src into DecodedLen(len(src)) bytes, returning the actual +// number of bytes written to dst. +// +// If Decode encounters invalid input, it returns an error describing the failure. +func (e *Encoding) Decode(dst, src []byte) (int, error) { + if len(src)%2 == 1 { + return 0, ErrLength + } + + for i := 0; i < len(src)/2; i++ { + a, ok := e.fromHexChar(src[i*2]) + if !ok { + return 0, InvalidByteError(src[i*2]) + } + b, ok := e.fromHexChar(src[i*2+1]) + if !ok { + return 0, InvalidByteError(src[i*2+1]) + } + dst[i] = (a << 4) | b + } + + return len(src) / 2, nil +} + +// fromHexChar converts a hex character into its value and a success flag. +func (e *Encoding) fromHexChar(c byte) (byte, bool) { + for i, ch := range []byte(e.hextable) { + if ch == c { + return byte(i), true + } + } + + return 0, false +} + +// EncodeToString returns the hexadecimal encoding of src. +func (e *Encoding) EncodeToString(src []byte) string { + dst := make([]byte, EncodedLen(len(src))) + e.Encode(dst, src) + return string(dst) +} + +// DecodeString returns the bytes represented by the hexadecimal string s. +func (e *Encoding) DecodeString(s string) ([]byte, error) { + src := []byte(s) + dst := make([]byte, DecodedLen(len(src))) + _, err := e.Decode(dst, src) + if err != nil { + return nil, err + } + return dst, nil +} diff --git a/vendor/github.com/getlantern/hidden/hidden.go b/vendor/github.com/getlantern/hidden/hidden.go new file mode 100644 index 0000000..0c52b08 --- /dev/null +++ b/vendor/github.com/getlantern/hidden/hidden.go @@ -0,0 +1,66 @@ +// Package hidden provides the ability to "hide" binary data in a string using +// a hex encoding with non-printing characters. Hidden data is demarcated with +// a leading and trailing NUL character. +package hidden + +import ( + "bytes" + "fmt" + "regexp" + + "github.com/getlantern/hex" +) + +// 16 non-printing characters +const hextable = "\x01\x02\x03\x04\x05\x06\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17" + +var ( + hexencoding = hex.NewEncoding(hextable) + + re *regexp.Regexp +) + +func init() { + var err error + re, err = regexp.Compile(fmt.Sprintf("\x00[%v]+\x00", hextable)) + if err != nil { + panic(err) + } +} + +// ToString encodes the given data as a hidden string, including leadnig and +// trailing NULs. +func ToString(data []byte) string { + buf := bytes.NewBuffer(make([]byte, 0, 2+hex.EncodedLen(len(data)))) + // Leading NUL + buf.WriteByte(0) + buf.WriteString(hexencoding.EncodeToString(data)) + // Trailing NUL + buf.WriteByte(0) + return buf.String() +} + +// FromString extracts the hidden data from a string, which is expected to +// contain leading and trailing NULs. +func FromString(str string) ([]byte, error) { + return hexencoding.DecodeString(str[1 : len(str)-1]) +} + +// Extract extracts all hidden data from an arbitrary string. +func Extract(str string) ([][]byte, error) { + m := re.FindAllString(str, -1) + result := make([][]byte, 0, len(m)) + for _, s := range m { + b, err := FromString(s) + if err != nil { + return nil, err + } + result = append(result, b) + } + return result, nil +} + +// Clean removes any hidden data from an arbitrary string. +func Clean(str string) string { + return re.ReplaceAllString(str, "") +} diff --git a/vendor/github.com/getlantern/hidden/hidden_test.go b/vendor/github.com/getlantern/hidden/hidden_test.go new file mode 100644 index 0000000..420ce64 --- /dev/null +++ b/vendor/github.com/getlantern/hidden/hidden_test.go @@ -0,0 +1,33 @@ +package hidden + +import ( + "encoding/binary" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestRoundTrip(t *testing.T) { + str := "H" + encoded := ToString([]byte(str)) + rt, err := FromString(encoded) + if assert.NoError(t, err) { + assert.Equal(t, str, string(rt)) + } +} + +func TestExtract(t *testing.T) { + a := []byte("Here is my string") + b := make([]byte, 8) + binary.BigEndian.PutUint64(b, 56) + str := fmt.Sprintf("hidden%s data%s is fun", ToString(a), ToString(b)) + t.Log(str) + out, err := Extract(str) + if assert.NoError(t, err) { + if assert.Len(t, out, 2) { + assert.Equal(t, out, [][]byte{a, b}) + } + } + assert.Equal(t, "hidden data is fun", Clean(str)) +} diff --git a/vendor/github.com/getlantern/ops/ops.go b/vendor/github.com/getlantern/ops/ops.go new file mode 100644 index 0000000..136302d --- /dev/null +++ b/vendor/github.com/getlantern/ops/ops.go @@ -0,0 +1,154 @@ +// Package ops provides a facility for tracking the processing of operations, +// including contextual metadata about the operation and their final success or +// failure. An op is assumed to have succeeded if by the time of calling Exit() +// no errors have been reported. The final status can be reported to a metrics +// facility. +package ops + +import ( + "sync" + "sync/atomic" + + "github.com/getlantern/context" +) + +var ( + cm = context.NewManager() + reporters []Reporter + reportersMutex sync.RWMutex +) + +// Reporter is a function that reports the success or failure of an Op. If +// failure is nil, the Op can be considered successful. +type Reporter func(failure error, ctx map[string]interface{}) + +// Op represents an operation that's being performed. It mimics the API of +// context.Context. +type Op interface { + // Begin marks the beginning of an Op under this Op. + Begin(name string) Op + + // Go starts the given function on a new goroutine. + Go(fn func()) + + // End marks the end of this op, at which point the Op will report its success + // or failure to all registered Reporters. + End() + + // Cancel cancels this op so that even if End() is called later, it will not + // report its success or failure. + Cancel() + + // Set puts a key->value pair into the current Op's context. + Set(key string, value interface{}) Op + + // SetDynamic puts a key->value pair into the current Op's context, where the + // value is generated by a function that gets evaluated at every Read. + SetDynamic(key string, valueFN func() interface{}) Op + + // FailIf marks this Op as failed if the given err is not nil. If FailIf is + // called multiple times, the latest error will be reported as the failure. + // Returns the original error for convenient chaining. + FailIf(err error) error +} + +type op struct { + ctx context.Context + canceled bool + failure atomic.Value +} + +// RegisterReporter registers the given reporter. +func RegisterReporter(reporter Reporter) { + reportersMutex.Lock() + reporters = append(reporters, reporter) + reportersMutex.Unlock() +} + +// Begin marks the beginning of a new Op. +func Begin(name string) Op { + return &op{ctx: cm.Enter().Put("op", name).PutIfAbsent("root_op", name)} +} + +func (o *op) Begin(name string) Op { + return &op{ctx: o.ctx.Enter().Put("op", name).PutIfAbsent("root_op", name)} +} + +func (o *op) Go(fn func()) { + o.ctx.Go(fn) +} + +// Go mimics the method from context.Manager. +func Go(fn func()) { + cm.Go(fn) +} + +func (o *op) Cancel() { + o.canceled = true +} + +func (o *op) End() { + if o.canceled { + return + } + + var reportersCopy []Reporter + reportersMutex.RLock() + if len(reporters) > 0 { + reportersCopy = make([]Reporter, len(reporters)) + copy(reportersCopy, reporters) + } + reportersMutex.RUnlock() + + if len(reportersCopy) > 0 { + var failure error + _failure := o.failure.Load() + ctx := o.ctx.AsMap(_failure, true) + if _failure != nil { + failure = _failure.(error) + _, errorSet := ctx["error"] + if !errorSet { + ctx["error"] = failure.Error() + } + } + for _, reporter := range reportersCopy { + reporter(failure, ctx) + } + } + + o.ctx.Exit() +} + +func (o *op) Set(key string, value interface{}) Op { + o.ctx.Put(key, value) + return o +} + +// SetGlobal puts a key->value pair into the global context, which is inherited +// by all Ops. +func SetGlobal(key string, value interface{}) { + cm.PutGlobal(key, value) +} + +func (o *op) SetDynamic(key string, valueFN func() interface{}) Op { + o.ctx.PutDynamic(key, valueFN) + return o +} + +// SetGlobalDynamic is like SetGlobal but uses a function to derive the value +// at read time. +func SetGlobalDynamic(key string, valueFN func() interface{}) { + cm.PutGlobalDynamic(key, valueFN) +} + +// AsMap mimics the method from context.Manager. +func AsMap(obj interface{}, includeGlobals bool) context.Map { + return cm.AsMap(obj, includeGlobals) +} + +func (o *op) FailIf(err error) error { + if err != nil { + o.failure.Store(err) + } + return err +} diff --git a/vendor/github.com/getlantern/ops/ops_test.go b/vendor/github.com/getlantern/ops/ops_test.go new file mode 100644 index 0000000..1c16e73 --- /dev/null +++ b/vendor/github.com/getlantern/ops/ops_test.go @@ -0,0 +1,76 @@ +package ops_test + +import ( + "sync" + "testing" + + "github.com/getlantern/errors" + "github.com/getlantern/ops" + "github.com/stretchr/testify/assert" +) + +func TestSuccess(t *testing.T) { + var reportedFailure error + var reportedCtx map[string]interface{} + report := func(failure error, ctx map[string]interface{}) { + reportedFailure = failure + reportedCtx = ctx + } + + ops.RegisterReporter(report) + ops.SetGlobal("g", "g1") + op := ops.Begin("test_success").Set("a", 1).SetDynamic("b", func() interface{} { return 2 }) + defer op.End() + innerOp := op.Begin("inside") + innerOp.FailIf(nil) + innerOp.End() + + assert.Nil(t, reportedFailure) + expectedCtx := map[string]interface{}{ + "op": "inside", + "root_op": "test_success", + "g": "g1", + "a": 1, + "b": 2, + } + assert.Equal(t, expectedCtx, reportedCtx) +} + +func TestFailure(t *testing.T) { + doTestFailure(t, false) +} + +func TestCancel(t *testing.T) { + doTestFailure(t, true) +} + +func doTestFailure(t *testing.T, cancel bool) { + var reportedFailure error + var reportedCtx map[string]interface{} + report := func(failure error, ctx map[string]interface{}) { + reportedFailure = failure + reportedCtx = ctx + } + + ops.RegisterReporter(report) + op := ops.Begin("test_failure") + var wg sync.WaitGroup + wg.Add(1) + op.Go(func() { + op.FailIf(errors.New("I failed").With("errorcontext", 5)) + wg.Done() + }) + wg.Wait() + if cancel { + op.Cancel() + } + op.End() + + if cancel { + assert.Nil(t, reportedFailure) + assert.Nil(t, reportedCtx) + } else { + assert.Contains(t, reportedFailure.Error(), "I failed") + assert.Equal(t, 5, reportedCtx["errorcontext"]) + } +} -- cgit v1.2.3