This commit is contained in:
Manfred Touron 2017-05-18 18:54:23 +02:00
parent dc386661ca
commit 5448f25fd6
No known key found for this signature in database
GPG Key ID: 9CCF47DF1FD978A1
645 changed files with 55908 additions and 33297 deletions

41
glide.lock generated
View File

@ -1,12 +1,12 @@
hash: 952872c40b8721f6bcd8bc1eab4f174aa0aebc5e8d9acb70ecdffb91068845b5 hash: 1944ae13e983e8da7b26c697fc40d79d34326b8c7f56c8939fb16f1ff8caca5b
updated: 2017-03-31T17:55:29.337081111+02:00 updated: 2017-05-18T19:20:01.855895064+02:00
imports: imports:
- name: github.com/aokoli/goutils - name: github.com/aokoli/goutils
version: 9c37978a95bd5c709a15883b6242714ea6709e64 version: e57d01ace047c1a43e6a49ecf3ecc50ed2be81d1
- name: github.com/dgrijalva/jwt-go - name: github.com/dgrijalva/jwt-go
version: d2709f9f1f31ebcda9651b03077758c1f3a0018c version: c9eaceb2896dbb515dae7ec352b377a226a52721
- name: github.com/go-kit/kit - name: github.com/go-kit/kit
version: fadad6fffe0466b19df9efd9acde5c9a52df5fa4 version: 9f5c614cd1e70102f80b644edbc760805ebf16d5
subpackages: subpackages:
- auth/jwt - auth/jwt
- endpoint - endpoint
@ -16,22 +16,28 @@ imports:
- name: github.com/go-logfmt/logfmt - name: github.com/go-logfmt/logfmt
version: 390ab7935ee28ec6b286364bba9b4dd6410cb3d5 version: 390ab7935ee28ec6b286364bba9b4dd6410cb3d5
- name: github.com/go-stack/stack - name: github.com/go-stack/stack
version: 100eb0c0a9c5b306ca2fb4f165df21d80ada4b82 version: 7a2f19628aabfe68f0766b59e74d6315f8347d22
- name: github.com/golang/glog
version: 23def4e6c14b4da8ac2ed8007337bc5eb5007998
- name: github.com/golang/protobuf - name: github.com/golang/protobuf
version: 2bba0603135d7d7f5cb73b2125beeda19c09f4ef version: 8ee79997227bf9b34611aee7946ae64735e6fd93
subpackages: subpackages:
- proto - proto
- protoc-gen-go/descriptor - protoc-gen-go/descriptor
- protoc-gen-go/generator - protoc-gen-go/generator
- protoc-gen-go/plugin - protoc-gen-go/plugin
- name: github.com/gorilla/handlers - name: github.com/gorilla/handlers
version: 13d73096a474cac93275c679c7b8a2dc17ddba82 version: e1b2144f2167de0e1042d1d35e5cba5119d4fb5d
- name: github.com/grpc-ecosystem/grpc-gateway - name: github.com/grpc-ecosystem/grpc-gateway
version: 18d159699f2e83fc5bb9ef2f79465ca3f3122676 version: 589b126116b5fc961939b3e156c29e4d9d58222f
subpackages: subpackages:
- third_party/googleapis/google/api - protoc-gen-grpc-gateway/descriptor
- protoc-gen-grpc-gateway/httprule
- utilities
- name: github.com/huandu/xstrings - name: github.com/huandu/xstrings
version: 3959339b333561bf62a38b424fd41517c2c90f40 version: 3959339b333561bf62a38b424fd41517c2c90f40
- name: github.com/imdario/mergo
version: 3e95a51e0639b4cf372f2ccf74c86749d747fbdc
- name: github.com/kr/fs - name: github.com/kr/fs
version: 2788f0dbd16903de03cb8186e5c7d97b69ad387b version: 2788f0dbd16903de03cb8186e5c7d97b69ad387b
- name: github.com/kr/logfmt - name: github.com/kr/logfmt
@ -39,16 +45,16 @@ imports:
- name: github.com/Masterminds/semver - name: github.com/Masterminds/semver
version: 59c29afe1a994eacb71c833025ca7acf874bb1da version: 59c29afe1a994eacb71c833025ca7acf874bb1da
- name: github.com/Masterminds/sprig - name: github.com/Masterminds/sprig
version: cd9291e11ed2b78cdc1991bdd3e8adf51d6236bb version: 2f4371ac162f912989f01cc2b6af4ba6660e6a30
- name: github.com/satori/go.uuid - name: github.com/satori/go.uuid
version: 879c5887cd475cd7864858769793b2ceb0d44feb version: 879c5887cd475cd7864858769793b2ceb0d44feb
- name: golang.org/x/crypto - name: golang.org/x/crypto
version: 3cb07270c9455e8ad27956a70891c962d121a228 version: 0fe963104e9d1877082f8fb38f816fcd97eb1d10
subpackages: subpackages:
- pbkdf2 - pbkdf2
- scrypt - scrypt
- name: golang.org/x/net - name: golang.org/x/net
version: ffcf1bedda3b04ebb15a168a59800a73d6dc0f4d version: da2b4fa28524a3baf148c1b94df4440267063c88
subpackages: subpackages:
- context - context
- context/ctxhttp - context/ctxhttp
@ -59,28 +65,25 @@ imports:
- lex/httplex - lex/httplex
- trace - trace
- name: golang.org/x/text - name: golang.org/x/text
version: c3b0cbb40eb9a862fdc91f79bed8c7ac8e7a36e8 version: a49bea13b776691cb1b49873e5d8df96ec74831a
subpackages: subpackages:
- secure/bidirule - secure/bidirule
- transform - transform
- unicode/bidi - unicode/bidi
- unicode/norm - unicode/norm
- name: google.golang.org/genproto - name: google.golang.org/genproto
version: de9f5e90fe9b278809363f08c2072d2f2a429de7 version: bb3573be0c484136831138976d444b8754777aff
subpackages: subpackages:
- googleapis/api/annotations - googleapis/api/annotations
- name: google.golang.org/grpc - name: google.golang.org/grpc
version: cdee119ee21e61eef7093a41ba148fa83585e143 version: 777daa17ff9b5daef1cfdf915088a2ada3332bf0
subpackages: subpackages:
- codes - codes
- credentials - credentials
- grpclog - grpclog
- internal - internal
- keepalive
- metadata - metadata
- naming - naming
- peer - peer
- stats
- tap
- transport - transport
testImports: [] testImports: []

View File

@ -12,3 +12,5 @@ import:
- package: google.golang.org/genproto - package: google.golang.org/genproto
subpackages: subpackages:
- googleapis/api/annotations - googleapis/api/annotations
- package: github.com/grpc-ecosystem/grpc-gateway
version: 1.2.2

View File

@ -1,4 +1,6 @@
# Sprig: Template functions for Go templates # Sprig: Template functions for Go templates
[![Stability: Sustained](https://masterminds.github.io/stability/sustained.svg)](https://masterminds.github.io/stability/sustained.html)
[![Build Status](https://travis-ci.org/Masterminds/sprig.svg?branch=master)](https://travis-ci.org/Masterminds/sprig)
The Go language comes with a [built-in template The Go language comes with a [built-in template
language](http://golang.org/pkg/text/template/), but not language](http://golang.org/pkg/text/template/), but not
@ -6,12 +8,15 @@ very many template functions. This library provides a group of commonly
used template functions. used template functions.
It is inspired by the template functions found in It is inspired by the template functions found in
[Twig](http://twig.sensiolabs.org/documentation). [Twig](http://twig.sensiolabs.org/documentation) and also in various
JavaScript libraries, such as [underscore.js](http://underscorejs.org/).
[![Build Status](https://travis-ci.org/Masterminds/sprig.svg?branch=master)](https://travis-ci.org/Masterminds/sprig)
## Usage ## Usage
Template developers can read the [Sprig function documentation](http://masterminds.github.io/sprig/) to
learn about the >100 template functions available.
For Go developers wishing to include Sprig as a library in their programs,
API documentation is available [at GoDoc.org](http://godoc.org/github.com/Masterminds/sprig), but API documentation is available [at GoDoc.org](http://godoc.org/github.com/Masterminds/sprig), but
read on for standard usage. read on for standard usage.
@ -54,241 +59,6 @@ Produces:
HELLO!HELLO!HELLO!HELLO!HELLO! HELLO!HELLO!HELLO!HELLO!HELLO!
``` ```
## Functions
### Date Functions
- date: Format a date, where a date is an integer type or a time.Time type, and
format is a time.Format formatting string.
- dateModify: Given a date, modify it with a duration: `date_modify "-1.5h" now`. If the duration doesn't
parse, it returns the time unaltered. See `time.ParseDuration` for info on duration strings.
- now: Current time.Time, for feeding into date-related functions.
- htmlDate: Format a date for use in the value field of an HTML "date" form element.
- dateInZone: Like date, but takes three arguments: format, timestamp,
timezone.
- htmlDateInZone: Like htmlDate, but takes two arguments: timestamp,
timezone.
### String Functions
- trim: strings.TrimSpace
- trimAll: strings.Trim, but with the argument order reversed `trimAll "$" "$5.00"` or `"$5.00 | trimAll "$"`
- trimSuffix: strings.TrimSuffix, but with the argument order reversed `trimSuffix "-" "5-"`
- trimPrefix: strings.TrimPrefix, but with the argument order reversed `trimPrefix "$" "$5"`
- upper: strings.ToUpper
- lower: strings.ToLower
- title: strings.Title
- repeat: strings.Repeat, but with the arguments switched: `repeat count str`. (This simplifies common pipelines)
- substr: Given string, start, and length, return a substr.
- nospace: Remove all spaces from a string. `h e l l o` becomes
`hello`.
- abbrev: Truncate a string with ellipses
- trunc: Truncate a string (no suffix). `trunc 5 "Hello World"` yields "hello".
- abbrevboth: Truncate both sides of a string with ellipses
- untitle: Remove title case
- intials: Given multiple words, return the first letter of each
word
- randAlphaNum: Generate a random alpha-numeric string
- randAlpha: Generate a random alphabetic string
- randAscii: Generate a random ASCII string, including symbols
- randNumeric: Generate a random numeric string
- wrap: Wrap text at the given column count
- wrapWith: Wrap text at the given column count, and with the given
string for a line terminator: `wrap 50 "\n\t" $string`
- contains: strings.Contains, but with the arguments switched: `contains "cat" "uncatch"`. (This simplifies common pipelines)
- hasPrefix: strings.hasPrefix, but with the arguments switched: `hasPrefix "cat" "catch"`.
- hasSuffix: strings.hasSuffix, but with the arguments switched: `hasSuffix "cat" "ducat"`.
- quote: Wrap strings in double quotes. `quote "a" "b"` returns `"a"
"b"`
- squote: Wrap strings in single quotes.
- cat: Concatenate strings, separating them by spaces. `cat $a $b $c`.
- indent: Indent a string using space characters. `indent 4 "foo\nbar"` produces " foo\n bar"
- replace: Replace an old with a new in a string: `$name | replace " " "-"`
- plural: Choose singular or plural based on length: `len $fish | plural
"one anchovy" "many anchovies"`
- uuidv4: Generate a UUID v4 string
- sha256sum: Generate a hex encoded sha256 hash of the input
- toString: Convert something to a string
### String Slice Functions:
- join: strings.Join, but as `join SEP SLICE`
- split: strings.Split, but as `split SEP STRING`. The results are returned
as a map with the indexes set to _N, where N is an integer starting from 0.
Use it like this: `{{$v := "foo/bar/baz" | split "/"}}{{$v._0}}` (Prints `foo`)
- splitList: strings.Split, but as `split SEP STRING`. The results are returned
as an array.
- toStrings: convert a list to a list of strings. 'list 1 2 3 | toStrings' produces '["1" "2" "3"]'
- sortAlpha: sort a list lexicographically.
### Integer Slice Functions:
- until: Given an integer, returns a slice of counting integers from 0 to one
less than the given integer: `range $i, $e := until 5`
- untilStep: Given start, stop, and step, return an integer slice starting at
'start', stopping at `stop`, and incrementing by 'step'. This is the same
as Python's long-form of 'range'.
### Conversions:
- atoi: Convert a string to an integer. 0 if the integer could not be parsed.
- int: Convert a string or numeric to an int
- int64: Convert a string or numeric to an int64
- float64: Convert a string or numeric to a float64
### Defaults:
- default: Give a default value. Used like this: {{trim " "| default "empty"}}.
Since trim produces an empty string, the default value is returned. For
things with a length (strings, slices, maps), len(0) will trigger the default.
For numbers, the value 0 will trigger the default. For booleans, false will
trigger the default. For structs, the default is never returned (there is
no clear empty condition). For everything else, nil value triggers a default.
- empty: Returns true if the given value is the zero value for that
type. Structs are always non-empty.
- coalesce: Given a list of items, return the first non-empty one.
This follows the same rules as 'empty'. `{{ coalesce .someVal 0 "hello" }}`
will return `.someVal` if set, or else return "hello". The 0 is skipped
because it is an empty value.
- compact: Return a copy of a list with all of the empty values removed.
`list 0 1 2 "" | compact` will return `[1 2]`
### OS:
- env: Read an environment variable.
- expandenv: Expand all environment variables in a string.
### File Paths:
- base: Return the last element of a path. https://golang.org/pkg/path#Base
- dir: Remove the last element of a path. https://golang.org/pkg/path#Dir
- clean: Clean a path to the shortest equivalent name. (e.g. remove "foo/.."
from "foo/../bar.html") https://golang.org/pkg/path#Clean
- ext: Get the extension for a file path: https://golang.org/pkg/path#Ext
- isAbs: Returns true if a path is absolute: https://golang.org/pkg/path#IsAbs
### Encoding:
- b32enc: Encode a string into a Base32 string
- b32dec: Decode a string from a Base32 string
- b64enc: Encode a string into a Base64 string
- b64dec: Decode a string from a Base64 string
### Data Structures:
- tuple: Takes an arbitrary list of items and returns a slice of items. Its
tuple-ish properties are mainly gained through the template idiom, and not
through an API provided here. WARNING: The implementation of tuple will
change in the future.
- list: An arbitrary ordered list of items. (This is prefered over tuple.)
- dict: Takes a list of name/values and returns a map[string]interface{}.
The first parameter is converted to a string and stored as a key, the
second parameter is treated as the value. And so on, with odds as keys and
evens as values. If the function call ends with an odd, the last key will
be assigned the empty string. Non-string keys are converted to strings as
follows: []byte are converted, fmt.Stringers will have String() called.
errors will have Error() called. All others will be passed through
fmt.Sprtinf("%v"). _dicts are unordered_.
List:
```
{{$t := list 1 "a" "foo"}}
{{index $t 2}}{{index $t 0 }}{{index $t 1}}
{{/* Prints foo1a *}}
```
Dict:
```
{{ $t := map "key1" "value1" "key2" "value2" }}
{{ $t.key2 }}
{{ /* Prints value2 *}}
```
### Lists Functions:
These are used to manipulate lists: `{{ list 1 2 3 | reverse | first }}`
- first: Get the first item in a 'list'. 'list 1 2 3 | first' prints '1'
- last: Get the last item in a 'list': 'list 1 2 3 | last ' prints '3'
- rest: Get all but the first item in a list: 'list 1 2 3 | rest' returns '[2 3]'
- initial: Get all but the last item in a list: 'list 1 2 3 | initial' returns '[1 2]'
- append: Add an item to the end of a list: 'append $list 4' adds '4' to the end of '$list'
- prepend: Add an item to the beginning of a list: 'prepend $list 4' puts 4 at the beginning of the list.
- reverse: Reverse the items in a list.
- uniq: Remove duplicates from a list.
- without: Return a list with the given values removed: 'without (list 1 2 3) 1' would return '[2 3]'
- has: Return 'tru' if the item is found in the list: 'has "foo" $list' will return 'true' if the list contains "foo"
### Dict Functions:
These are used to manipulate dicts.
- set: Takes a dict, a key, and a value, and sets that key/value pair in
the dict. `set $dict $key $value`. For convenience, it returns the dict,
even though the dict was modified in place.
- unset: Takes a dict and a key, and deletes that key/value pair from the
dict. `unset $dict $key`. This returns the dict for convenience.
- hasKey: Takes a dict and a key, and returns boolean true if the key is in
the dict.
- pluck: Given a key and one or more maps, get all of the values for that key.
- keys: Get an array of all of the keys in a dict. Order is not guaranteed.
- pick: Select just the given keys out of the dict, and return a new dict.
- omit: Return a dict without the given keys.
### Reflection:
- typeOf: Takes an interface and returns a string representation of the type.
For pointers, this will return a type prefixed with an asterisk(`*`). So
a pointer to type `Foo` will be `*Foo`.
- typeIs: Compares an interface with a string name, and returns true if they match.
Note that a pointer will not match a reference. For example `*Foo` will not
match `Foo`.
- typeIsLike: returns true if the interface is of the given type, or
is a pointer to the given type.
- kindOf: Takes an interface and returns a string representation of its kind.
- kindIs: Returns true if the given string matches the kind of the given interface.
Note: None of these can test whether or not something implements a given
interface, since doing so would require compiling the interface in ahead of
time.
### Math Functions:
Integer functions will convert integers of any width to `int64`. If a
string is passed in, functions will attempt to conver with
`strconv.ParseInt(s, 1064)`. If this fails, the value will be treated as 0.
- add1: Increment an integer by 1
- add: Sum integers. `add 1 2 3` renders `6`
- sub: Subtract the second integer from the first
- div: Divide the first integer by the second
- mod: Module of first integer divided by second
- mul: Multiply integers integers
- max (biggest): Return the biggest of a series of integers. `max 1 2 3`
returns `3`.
- min: Return the smallest of a series of integers. `min 1 2 3` returns
`1`.
### Cryptographic Functions:
- derivePassword: Derive a password from the given parameters according to the "Master Password" algorithm (http://masterpasswordapp.com/algorithm.html)
Given parameters (in order) are:
`counter` (starting with 1), `password_type` (maximum, long, medium, short, basic, or pin), `password`,
`user`, and `site`. The following line generates a long password for the user "user" and with a master-password "password" on the site "example.com":
```
{{ derivePassword 1 "long" "password" "user" "example.com" }}
```
## SemVer Functions:
These functions provide version parsing and comparisons for SemVer 2 version
strings.
- semver: Parse a semantic version and return a Version object.
- semverCompare: Compare a SemVer range to a particular version.
## Principles: ## Principles:
The following principles were used in deciding on which functions to add, and The following principles were used in deciding on which functions to add, and

View File

@ -1,6 +1,7 @@
package sprig package sprig
import ( import (
"encoding/json"
"reflect" "reflect"
) )
@ -60,3 +61,15 @@ func coalesce(v ...interface{}) interface{} {
} }
return nil return nil
} }
// toJson encodes an item into a JSON string
func toJson(v interface{}) string {
output, _ := json.Marshal(v)
return string(output)
}
// toPrettyJson encodes an item into a pretty (indented) JSON string
func toPrettyJson(v interface{}) string {
output, _ := json.MarshalIndent(v, "", " ")
return string(output)
}

View File

@ -82,3 +82,26 @@ func TestCoalesce(t *testing.T) {
t.Error(err) t.Error(err)
} }
} }
func TestToJson(t *testing.T) {
dict := map[string]interface{}{"Top": map[string]interface{}{"bool": true, "string": "test", "number": 42}}
tpl := `{{.Top | toJson}}`
expected := `{"bool":true,"number":42,"string":"test"}`
if err := runtv(tpl, expected, dict); err != nil {
t.Error(err)
}
}
func TestToPrettyJson(t *testing.T) {
dict := map[string]interface{}{"Top": map[string]interface{}{"bool": true, "string": "test", "number": 42}}
tpl := `{{.Top | toPrettyJson}}`
expected := `{
"bool": true,
"number": 42,
"string": "test"
}`
if err := runtv(tpl, expected, dict); err != nil {
t.Error(err)
}
}

View File

@ -1,5 +1,7 @@
package sprig package sprig
import "github.com/imdario/mergo"
func set(d map[string]interface{}, key string, value interface{}) map[string]interface{} { func set(d map[string]interface{}, key string, value interface{}) map[string]interface{} {
d[key] = value d[key] = value
return d return d
@ -72,3 +74,11 @@ func dict(v ...interface{}) map[string]interface{} {
} }
return dict return dict
} }
func merge(dst map[string]interface{}, src map[string]interface{}) interface{} {
if err := mergo.Merge(&dst, src); err != nil {
// Swallow errors inside of a template.
return ""
}
return dst
}

View File

@ -135,3 +135,40 @@ func TestCompact(t *testing.T) {
assert.NoError(t, runt(tpl, expect)) assert.NoError(t, runt(tpl, expect))
} }
} }
func TestMerge(t *testing.T) {
dict := map[string]interface{}{
"src": map[string]interface{}{
"a": 1,
"b": 2,
"d": map[string]interface{}{
"e": "four",
},
"g": []int{6, 7},
},
"dst": map[string]interface{}{
"a": "one",
"c": 3,
"d": map[string]interface{}{
"f": 5,
},
"g": []int{8, 9},
},
}
tpl := `{{merge .dst .src}}`
_, err := runRaw(tpl, dict)
if err != nil {
t.Error(err)
}
expected := map[string]interface{}{
"a": "one", // key overridden
"b": 2, // merged from src
"c": 3, // merged from dst
"d": map[string]interface{}{ // deep merge
"e": "four",
"f": 5,
},
"g": []int{8, 9}, // overridden - arrays are not merged
}
assert.Equal(t, expected, dict["dst"])
}

View File

@ -19,7 +19,7 @@ Date Functions
- date FORMAT TIME: Format a date, where a date is an integer type or a time.Time type, and - date FORMAT TIME: Format a date, where a date is an integer type or a time.Time type, and
format is a time.Format formatting string. format is a time.Format formatting string.
- dateModify: Given a date, modify it with a duration: `date_modify "-1.5h" now`. If the duration doesn't - dateModify: Given a date, modify it with a duration: `date_modify "-1.5h" now`. If the duration doesn't
parse, it returns the time unaltered. See `time.ParseDuration` for info on duration strings. parse, it returns the time unaltered. See `time.ParseDuration` for info on duration strings.
- now: Current time.Time, for feeding into date-related functions. - now: Current time.Time, for feeding into date-related functions.
- htmlDate TIME: Format a date for use in the value field of an HTML "date" form element. - htmlDate TIME: Format a date for use in the value field of an HTML "date" form element.
- dateInZone FORMAT TIME TZ: Like date, but takes three arguments: format, timestamp, - dateInZone FORMAT TIME TZ: Like date, but takes three arguments: format, timestamp,
@ -68,7 +68,7 @@ String Slice Functions:
- split: strings.Split, but as `split SEP STRING`. The results are returned - split: strings.Split, but as `split SEP STRING`. The results are returned
as a map with the indexes set to _N, where N is an integer starting from 0. as a map with the indexes set to _N, where N is an integer starting from 0.
Use it like this: `{{$v := "foo/bar/baz" | split "/"}}{{$v._0}}` (Prints `foo`) Use it like this: `{{$v := "foo/bar/baz" | split "/"}}{{$v._0}}` (Prints `foo`)
- splitList: strings.Split, but as `split SEP STRING`. The results are returned - splitList: strings.Split, but as `split SEP STRING`. The results are returned
as an array. as an array.
- toStrings: convert a list to a list of strings. 'list 1 2 3 | toStrings' produces '["1" "2" "3"]' - toStrings: convert a list to a list of strings. 'list 1 2 3 | toStrings' produces '["1" "2" "3"]'
- sortAlpha: sort a list lexicographically. - sortAlpha: sort a list lexicographically.
@ -114,7 +114,7 @@ File Paths:
- base: Return the last element of a path. https://golang.org/pkg/path#Base - base: Return the last element of a path. https://golang.org/pkg/path#Base
- dir: Remove the last element of a path. https://golang.org/pkg/path#Dir - dir: Remove the last element of a path. https://golang.org/pkg/path#Dir
- clean: Clean a path to the shortest equivalent name. (e.g. remove "foo/.." - clean: Clean a path to the shortest equivalent name. (e.g. remove "foo/.."
from "foo/../bar.html") https://golang.org/pkg/path#Clean from "foo/../bar.html") https://golang.org/pkg/path#Clean
- ext: https://golang.org/pkg/path#Ext - ext: https://golang.org/pkg/path#Ext
- isAbs: https://golang.org/pkg/path#IsAbs - isAbs: https://golang.org/pkg/path#IsAbs

View File

@ -75,6 +75,15 @@ inserted.
A common idiom in Sprig templates is to uses `pluck... | first` to get the first A common idiom in Sprig templates is to uses `pluck... | first` to get the first
matching key out of a collection of dictionaries. matching key out of a collection of dictionaries.
## merge
Merge two dictionaries into one, giving precedence to the dest dictionary:
```
$newdict := merge $dest $source
```
This is a deep merge operation.
## keys ## keys

View File

@ -0,0 +1,11 @@
# Flow Control Functions
## fail
Unconditionally returns an empty `string` and an `error` with the specified
text. This is useful in scenarios where other conditionals have determined that
template rendering should fail.
```
fail "Please accept the end user license agreement"
```

View File

@ -2,21 +2,22 @@
The Sprig library provides over 70 template functions for Go's template language. The Sprig library provides over 70 template functions for Go's template language.
- [String Functions](strings.html): `trim`, `wrap`, `randAlpha`, `plural`, etc. - [String Functions](strings.md): `trim`, `wrap`, `randAlpha`, `plural`, etc.
- [String List Functions](string_slice.html): `splitList`, `sortAlpha`, etc. - [String List Functions](string_slice.md): `splitList`, `sortAlpha`, etc.
- [Math Functions](math.html): `add`, `max`, `mul`, etc. - [Math Functions](math.md): `add`, `max`, `mul`, etc.
- [Integer Slice Functions](integer_slice.html): `until`, `untilStep` - [Integer Slice Functions](integer_slice.md): `until`, `untilStep`
- [Date Functions](date.html): `now`, `date`, etc. - [Date Functions](date.md): `now`, `date`, etc.
- [Defaults Functions](defaults.html): `default`, `empty`, `coalesce` - [Defaults Functions](defaults.md): `default`, `empty`, `coalesce`
- [Encoding Functions](encoding.html): `b64enc`, `b64dec`, etc. - [Encoding Functions](encoding.md): `b64enc`, `b64dec`, etc.
- [Lists and List Functions](lists.html): `list`, `first`, `uniq`, etc. - [Lists and List Functions](lists.md): `list`, `first`, `uniq`, etc.
- [Dictionaries and Dict Functions](dicts.html): `dict`, `hasKey`, `pluck`, etc. - [Dictionaries and Dict Functions](dicts.md): `dict`, `hasKey`, `pluck`, etc.
- [Type Conversion Functions](conversion.html): `atoi`, `int64`, `toString`, etc. - [Type Conversion Functions](conversion.md): `atoi`, `int64`, `toString`, etc.
- [File Path Functions](paths.html): `base`, `dir`, `ext`, `clean`, `isAbs` - [File Path Functions](paths.md): `base`, `dir`, `ext`, `clean`, `isAbs`
- [Flow Control Functions](flow_control.md): `fail`
- Advanced Functions - Advanced Functions
- [UUID Functions](uuid.html): `uuidv4` - [UUID Functions](uuid.md): `uuidv4`
- [OS Functions](os.html): `env`, `expandenv` - [OS Functions](os.md): `env`, `expandenv`
- [Version Comparison Functions](semver.html): `semver`, `semverCompare` - [Version Comparison Functions](semver.md): `semver`, `semverCompare`
- [Reflection](reflection.html): `typeOf`, `kindIs`, `typeIsLike`, etc. - [Reflection](reflection.md): `typeOf`, `kindIs`, `typeIsLike`, etc.
- [Cryptographic and Security Functions](crypto.html): `derivePassword`, `sha256sum`, `genPrivateKey` - [Cryptographic and Security Functions](crypto.md): `derivePassword`, `sha256sum`, `genPrivateKey`

View File

@ -278,6 +278,43 @@ rules. And `0` is considered a plural because the English language treats it
as such (`zero anchovies`). The Sprig developers are working on a solution for as such (`zero anchovies`). The Sprig developers are working on a solution for
better internationalization. better internationalization.
## snakecase
Convert string from camelCase to snake_case.
Introduced in 2.12.0.
```
snakecase "FirstName"
```
This above will produce `first_name`.
## camelcase
Convert string from snake_case to CamelCase
Introduced in 2.12.0.
```
camelcase "http_server"
```
This above will produce `HttpServer`.
## shuffle
Shuffle a string.
Introduced in 2.12.0.
```
shuffle "hello"
```
The above will randomize the letters in `hello`, perhaps producing `oelhl`.
## See Also... ## See Also...
The [Conversion Functions](conversion.html) contain functions for converting The [Conversion Functions](conversion.html) contain functions for converting

View File

@ -0,0 +1,16 @@
package sprig
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
func TestFail(t *testing.T) {
const msg = "This is an error!"
tpl := fmt.Sprintf(`{{fail "%s"}}`, msg)
_, err := runRaw(tpl, nil)
assert.Error(t, err)
assert.Contains(t, err.Error(), msg)
}

View File

@ -1,6 +1,7 @@
package sprig package sprig
import ( import (
"errors"
"html/template" "html/template"
"os" "os"
"path" "path"
@ -10,6 +11,7 @@ import (
"time" "time"
util "github.com/aokoli/goutils" util "github.com/aokoli/goutils"
"github.com/huandu/xstrings"
) )
// Produce the function map. // Produce the function map.
@ -122,6 +124,9 @@ var genericMap = map[string]interface{}{
"randAscii": randAscii, "randAscii": randAscii,
"randNumeric": randNumeric, "randNumeric": randNumeric,
"swapcase": util.SwapCase, "swapcase": util.SwapCase,
"shuffle": xstrings.Shuffle,
"snakecase": xstrings.ToSnakeCase,
"camelcase": xstrings.ToCamelCase,
"wrap": func(l int, s string) string { return util.Wrap(s, l) }, "wrap": func(l int, s string) string { return util.Wrap(s, l) },
"wrapWith": func(l int, sep, str string) string { return util.WrapCustom(str, l, sep, true) }, "wrapWith": func(l int, sep, str string) string { return util.WrapCustom(str, l, sep, true) },
// Switch order so that "foobar" | contains "foo" // Switch order so that "foobar" | contains "foo"
@ -185,10 +190,12 @@ var genericMap = map[string]interface{}{
"sortAlpha": sortAlpha, "sortAlpha": sortAlpha,
// Defaults // Defaults
"default": dfault, "default": dfault,
"empty": empty, "empty": empty,
"coalesce": coalesce, "coalesce": coalesce,
"compact": compact, "compact": compact,
"toJson": toJson,
"toPrettyJson": toPrettyJson,
// Reflection // Reflection
"typeOf": typeOf, "typeOf": typeOf,
@ -225,6 +232,7 @@ var genericMap = map[string]interface{}{
"keys": keys, "keys": keys,
"pick": pick, "pick": pick,
"omit": omit, "omit": omit,
"merge": merge,
"append": push, "push": push, "append": push, "push": push,
"prepend": prepend, "prepend": prepend,
@ -247,4 +255,7 @@ var genericMap = map[string]interface{}{
// SemVer: // SemVer:
"semver": semver, "semver": semver,
"semverCompare": semverCompare, "semverCompare": semverCompare,
// Flow Control:
"fail": func(msg string) (string, error) { return "", errors.New(msg) },
} }

View File

@ -3,10 +3,12 @@ package sprig
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"math/rand"
"os" "os"
"testing" "testing"
"text/template" "text/template"
"github.com/aokoli/goutils"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -47,6 +49,30 @@ func TestExt(t *testing.T) {
assert.NoError(t, runt(`{{ ext "/foo/bar/baz.txt" }}`, ".txt")) assert.NoError(t, runt(`{{ ext "/foo/bar/baz.txt" }}`, ".txt"))
} }
func TestSnakeCase(t *testing.T) {
assert.NoError(t, runt(`{{ snakecase "FirstName" }}`, "first_name"))
assert.NoError(t, runt(`{{ snakecase "HTTPServer" }}`, "http_server"))
assert.NoError(t, runt(`{{ snakecase "NoHTTPS" }}`, "no_https"))
assert.NoError(t, runt(`{{ snakecase "GO_PATH" }}`, "go_path"))
assert.NoError(t, runt(`{{ snakecase "GO PATH" }}`, "go_path"))
assert.NoError(t, runt(`{{ snakecase "GO-PATH" }}`, "go_path"))
}
func TestCamelCase(t *testing.T) {
assert.NoError(t, runt(`{{ camelcase "http_server" }}`, "HttpServer"))
assert.NoError(t, runt(`{{ camelcase "_camel_case" }}`, "_CamelCase"))
assert.NoError(t, runt(`{{ camelcase "no_https" }}`, "NoHttps"))
assert.NoError(t, runt(`{{ camelcase "_complex__case_" }}`, "_Complex_Case_"))
assert.NoError(t, runt(`{{ camelcase "all" }}`, "All"))
}
func TestShuffle(t *testing.T) {
goutils.RANDOM = rand.New(rand.NewSource(1))
// Because we're using a random number generator, we need these to go in
// a predictable sequence:
assert.NoError(t, runt(`{{ shuffle "Hello World" }}`, "rldo HWlloe"))
}
// runt runs a template and checks that the output exactly matches the expected string. // runt runs a template and checks that the output exactly matches the expected string.
func runt(tpl, expect string) error { func runt(tpl, expect string) error {
return runtv(tpl, expect, map[string]string{}) return runtv(tpl, expect, map[string]string{})

View File

@ -1,14 +1,24 @@
hash: c2d7cb87ff32a0aba767b90bff630c3e4c1ca9904fc72568414d20d79a41e70f hash: b9cc40bfd6dde74a94103b96700df1a9ab29a7fff5650216cf5a05f4fe72fb73
updated: 2017-03-13T18:38:30.597881175-06:00 updated: 2017-05-02T16:01:04.617727646-06:00
imports: imports:
- name: github.com/aokoli/goutils - name: github.com/aokoli/goutils
version: 9c37978a95bd5c709a15883b6242714ea6709e64 version: 9c37978a95bd5c709a15883b6242714ea6709e64
- name: github.com/huandu/xstrings
version: 3959339b333561bf62a38b424fd41517c2c90f40
- name: github.com/imdario/mergo
version: 3e95a51e0639b4cf372f2ccf74c86749d747fbdc
- name: github.com/Masterminds/goutils
version: 45307ec16e3cd47cd841506c081f7afd8237d210
- name: github.com/Masterminds/semver - name: github.com/Masterminds/semver
version: 59c29afe1a994eacb71c833025ca7acf874bb1da version: 59c29afe1a994eacb71c833025ca7acf874bb1da
- name: github.com/satori/go.uuid - name: github.com/satori/go.uuid
version: 879c5887cd475cd7864858769793b2ceb0d44feb version: 879c5887cd475cd7864858769793b2ceb0d44feb
- name: github.com/stretchr/testify
version: e3a8ff8ce36581f87a15341206f205b1da467059
subpackages:
- assert
- name: golang.org/x/crypto - name: golang.org/x/crypto
version: 1f22c0103821b9390939b6776727195525381532 version: d172538b2cfce0c13cee31e647d0367aa8cd2486
subpackages: subpackages:
- pbkdf2 - pbkdf2
- scrypt - scrypt
@ -21,7 +31,3 @@ testImports:
version: d8ed2627bdf02c080bf22230dbb337003b7aba2d version: d8ed2627bdf02c080bf22230dbb337003b7aba2d
subpackages: subpackages:
- difflib - difflib
- name: github.com/stretchr/testify
version: e3a8ff8ce36581f87a15341206f205b1da467059
subpackages:
- assert

View File

@ -1,6 +1,7 @@
package: github.com/Masterminds/sprig package: github.com/Masterminds/sprig
import: import:
- package: github.com/aokoli/goutils - package: github.com/Masterminds/goutils
version: ^1.0.0
- package: github.com/satori/go.uuid - package: github.com/satori/go.uuid
version: ^1.1.0 version: ^1.1.0
- package: golang.org/x/crypto - package: golang.org/x/crypto
@ -8,3 +9,7 @@ import:
- scrypt - scrypt
- package: github.com/Masterminds/semver - package: github.com/Masterminds/semver
version: v1.2.2 version: v1.2.2
- package: github.com/stretchr/testify
- package: github.com/imdario/mergo
version: ~0.2.2
- package: github.com/huandu/xstrings

18
vendor/github.com/aokoli/goutils/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,18 @@
language: go
go:
- 1.6
- 1.7
- 1.8
- tip
script:
- go test -v
notifications:
webhooks:
urls:
- https://webhooks.gitter.im/e/06e3328629952dabe3e0
on_success: change # options: [always|never|change] default: always
on_failure: always # options: [always|never|change] default: always
on_start: never # options: [always|never|change] default: always

View File

@ -1,5 +1,8 @@
GoUtils GoUtils
=========== ===========
[![Stability: Maintenance](https://masterminds.github.io/stability/maintenance.svg)](https://masterminds.github.io/stability/maintenance.html)
[![GoDoc](https://godoc.org/github.com/Masterminds/goutils?status.png)](https://godoc.org/github.com/Masterminds/goutils) [![Build Status](https://travis-ci.org/Masterminds/goutils.svg?branch=master)](https://travis-ci.org/Masterminds/goutils) [![Build status](https://ci.appveyor.com/api/projects/status/sc2b1ew0m7f0aiju?svg=true)](https://ci.appveyor.com/project/mattfarina/goutils)
GoUtils provides users with utility functions to manipulate strings in various ways. It is a Go implementation of some GoUtils provides users with utility functions to manipulate strings in various ways. It is a Go implementation of some
string manipulation libraries of Java Apache Commons. GoUtils includes the following Java Apache Commons classes: string manipulation libraries of Java Apache Commons. GoUtils includes the following Java Apache Commons classes:
@ -10,13 +13,13 @@ string manipulation libraries of Java Apache Commons. GoUtils includes the follo
## Installation ## Installation
If you have Go set up on your system, from the GOPATH directory within the command line/terminal, enter this: If you have Go set up on your system, from the GOPATH directory within the command line/terminal, enter this:
go get github.com/aokoli/goutils go get github.com/Masterminds/goutils
If you do not have Go set up on your system, please follow the [Go installation directions from the documenation](http://golang.org/doc/install), and then follow the instructions above to install GoUtils. If you do not have Go set up on your system, please follow the [Go installation directions from the documenation](http://golang.org/doc/install), and then follow the instructions above to install GoUtils.
## Documentation ## Documentation
GoUtils doc is available here: [![GoDoc](https://godoc.org/github.com/aokoli/goutils?status.png)](https://godoc.org/github.com/aokoli/goutils) GoUtils doc is available here: [![GoDoc](https://godoc.org/github.com/Masterminds/goutils?status.png)](https://godoc.org/github.com/Masterminds/goutils)
## Usage ## Usage
@ -26,7 +29,7 @@ The code snippets below show examples of how to use GoUtils. Some functions retu
import ( import (
"fmt" "fmt"
"github.com/aokoli/goutils" "github.com/Masterminds/goutils"
) )
func main() { func main() {
@ -41,7 +44,7 @@ Some functions return errors mainly due to illegal arguements used as parameters
import ( import (
"fmt" "fmt"
"github.com/aokoli/goutils" "github.com/Masterminds/goutils"
) )
func main() { func main() {
@ -61,12 +64,7 @@ Some functions return errors mainly due to illegal arguements used as parameters
GoUtils is licensed under the Apache License, Version 2.0. Please check the LICENSE.txt file or visit http://www.apache.org/licenses/LICENSE-2.0 for a copy of the license. GoUtils is licensed under the Apache License, Version 2.0. Please check the LICENSE.txt file or visit http://www.apache.org/licenses/LICENSE-2.0 for a copy of the license.
## Issue Reporting ## Issue Reporting
Make suggestions or report issues using the Git issue tracker: https://github.com/aokoli/goutils/issues Make suggestions or report issues using the Git issue tracker: https://github.com/Masterminds/goutils/issues
## Website ## Website
* [GoUtils webpage](http://aokoli.github.io/goutils/) * [GoUtils webpage](http://Masterminds.github.io/goutils/)
## Mailing List
Contact [okolialex@gmail.com](mailto:okolialex@mail.com) to be added to the mailing list. You will get updates on the
status of the project and the potential direction it will be heading.

21
vendor/github.com/aokoli/goutils/appveyor.yml generated vendored Normal file
View File

@ -0,0 +1,21 @@
version: build-{build}.{branch}
clone_folder: C:\gopath\src\github.com\Masterminds\goutils
shallow_clone: true
environment:
GOPATH: C:\gopath
platform:
- x64
build: off
install:
- go version
- go env
test_script:
- go test -v
deploy: off

View File

@ -16,17 +16,15 @@ limitations under the License.
package goutils package goutils
import ( import (
"fmt" "fmt"
"unicode"
"math" "math"
"math/rand" "math/rand"
"time" "time"
"unicode"
) )
// RANDOM provides the time-based seed used to generate random numbers
// Provides the time-based seed used to generate random #s
var RANDOM = rand.New(rand.NewSource(time.Now().UnixNano())) var RANDOM = rand.New(rand.NewSource(time.Now().UnixNano()))
/* /*
@ -40,11 +38,10 @@ Returns:
string - the random string string - the random string
error - an error stemming from an invalid parameter within underlying function, RandomSeed(...) error - an error stemming from an invalid parameter within underlying function, RandomSeed(...)
*/ */
func RandomNonAlphaNumeric (count int) (string, error) { func RandomNonAlphaNumeric(count int) (string, error) {
return RandomAlphaNumericCustom(count, false, false) return RandomAlphaNumericCustom(count, false, false)
} }
/* /*
RandomAscii creates a random string whose length is the number of characters specified. RandomAscii creates a random string whose length is the number of characters specified.
Characters will be chosen from the set of characters whose ASCII value is between 32 and 126 (inclusive). Characters will be chosen from the set of characters whose ASCII value is between 32 and 126 (inclusive).
@ -57,10 +54,9 @@ Returns:
error - an error stemming from an invalid parameter within underlying function, RandomSeed(...) error - an error stemming from an invalid parameter within underlying function, RandomSeed(...)
*/ */
func RandomAscii(count int) (string, error) { func RandomAscii(count int) (string, error) {
return Random(count, 32, 127, false, false) return Random(count, 32, 127, false, false)
} }
/* /*
RandomNumeric creates a random string whose length is the number of characters specified. RandomNumeric creates a random string whose length is the number of characters specified.
Characters will be chosen from the set of numeric characters. Characters will be chosen from the set of numeric characters.
@ -72,11 +68,10 @@ Returns:
string - the random string string - the random string
error - an error stemming from an invalid parameter within underlying function, RandomSeed(...) error - an error stemming from an invalid parameter within underlying function, RandomSeed(...)
*/ */
func RandomNumeric (count int) (string, error) { func RandomNumeric(count int) (string, error) {
return Random(count, 0, 0, false, true) return Random(count, 0, 0, false, true)
} }
/* /*
RandomAlphabetic creates a random string whose length is the number of characters specified. RandomAlphabetic creates a random string whose length is the number of characters specified.
Characters will be chosen from the set of alpha-numeric characters as indicated by the arguments. Characters will be chosen from the set of alpha-numeric characters as indicated by the arguments.
@ -90,11 +85,10 @@ Returns:
string - the random string string - the random string
error - an error stemming from an invalid parameter within underlying function, RandomSeed(...) error - an error stemming from an invalid parameter within underlying function, RandomSeed(...)
*/ */
func RandomAlphabetic (count int) (string, error) { func RandomAlphabetic(count int) (string, error) {
return Random(count, 0, 0, true, false) return Random(count, 0, 0, true, false)
} }
/* /*
RandomAlphaNumeric creates a random string whose length is the number of characters specified. RandomAlphaNumeric creates a random string whose length is the number of characters specified.
Characters will be chosen from the set of alpha-numeric characters. Characters will be chosen from the set of alpha-numeric characters.
@ -106,8 +100,8 @@ Returns:
string - the random string string - the random string
error - an error stemming from an invalid parameter within underlying function, RandomSeed(...) error - an error stemming from an invalid parameter within underlying function, RandomSeed(...)
*/ */
func RandomAlphaNumeric (count int) (string, error) { func RandomAlphaNumeric(count int) (string, error) {
return Random(count, 0, 0, true, true) return Random(count, 0, 0, true, true)
} }
/* /*
@ -123,11 +117,10 @@ Returns:
string - the random string string - the random string
error - an error stemming from an invalid parameter within underlying function, RandomSeed(...) error - an error stemming from an invalid parameter within underlying function, RandomSeed(...)
*/ */
func RandomAlphaNumericCustom (count int, letters bool, numbers bool) (string, error) { func RandomAlphaNumericCustom(count int, letters bool, numbers bool) (string, error) {
return Random(count, 0, 0, letters, numbers) return Random(count, 0, 0, letters, numbers)
} }
/* /*
Random creates a random string based on a variety of options, using default source of randomness. Random creates a random string based on a variety of options, using default source of randomness.
This method has exactly the same semantics as RandomSeed(int, int, int, bool, bool, []char, *rand.Rand), but This method has exactly the same semantics as RandomSeed(int, int, int, bool, bool, []char, *rand.Rand), but
@ -145,11 +138,10 @@ Returns:
string - the random string string - the random string
error - an error stemming from an invalid parameter within underlying function, RandomSeed(...) error - an error stemming from an invalid parameter within underlying function, RandomSeed(...)
*/ */
func Random (count int, start int, end int, letters bool, numbers bool, chars ...rune) (string, error) { func Random(count int, start int, end int, letters bool, numbers bool, chars ...rune) (string, error) {
return RandomSeed (count, start, end, letters, numbers, chars, RANDOM) return RandomSeed(count, start, end, letters, numbers, chars, RANDOM)
} }
/* /*
RandomSeed creates a random string based on a variety of options, using supplied source of randomness. RandomSeed creates a random string based on a variety of options, using supplied source of randomness.
If the parameters start and end are both 0, start and end are set to ' ' and 'z', the ASCII printable characters, will be used, If the parameters start and end are both 0, start and end are set to ' ' and 'z', the ASCII printable characters, will be used,
@ -171,89 +163,88 @@ Returns:
string - the random string string - the random string
error - an error stemming from invalid parameters: if count < 0; or the provided chars array is empty; or end <= start; or end > len(chars) error - an error stemming from invalid parameters: if count < 0; or the provided chars array is empty; or end <= start; or end > len(chars)
*/ */
func RandomSeed (count int, start int, end int, letters bool, numbers bool, chars []rune, random *rand.Rand) (string, error) { func RandomSeed(count int, start int, end int, letters bool, numbers bool, chars []rune, random *rand.Rand) (string, error) {
if count == 0 { if count == 0 {
return "", nil return "", nil
} else if count < 0 { } else if count < 0 {
err := fmt.Errorf("randomstringutils illegal argument: Requested random string length %v is less than 0.", count) // equiv to err := errors.New("...") err := fmt.Errorf("randomstringutils illegal argument: Requested random string length %v is less than 0.", count) // equiv to err := errors.New("...")
return "", err return "", err
} }
if chars != nil && len(chars) == 0 { if chars != nil && len(chars) == 0 {
err := fmt.Errorf("randomstringutils illegal argument: The chars array must not be empty") err := fmt.Errorf("randomstringutils illegal argument: The chars array must not be empty")
return "", err return "", err
} }
if start == 0 && end == 0 { if start == 0 && end == 0 {
if chars != nil { if chars != nil {
end = len(chars) end = len(chars)
} else { } else {
if !letters && !numbers { if !letters && !numbers {
end = math.MaxInt32 end = math.MaxInt32
} else { } else {
end = 'z' + 1 end = 'z' + 1
start = ' ' start = ' '
} }
} }
} else { } else {
if end <= start { if end <= start {
err := fmt.Errorf("randomstringutils illegal argument: Parameter end (%v) must be greater than start (%v)", end, start) err := fmt.Errorf("randomstringutils illegal argument: Parameter end (%v) must be greater than start (%v)", end, start)
return "", err return "", err
} }
if chars != nil && end > len(chars) { if chars != nil && end > len(chars) {
err := fmt.Errorf("randomstringutils illegal argument: Parameter end (%v) cannot be greater than len(chars) (%v)", end, len(chars)) err := fmt.Errorf("randomstringutils illegal argument: Parameter end (%v) cannot be greater than len(chars) (%v)", end, len(chars))
return "", err return "", err
} }
} }
buffer := make([]rune, count) buffer := make([]rune, count)
gap := end - start gap := end - start
// high-surrogates range, (\uD800-\uDBFF) = 55296 - 56319
// low-surrogates range, (\uDC00-\uDFFF) = 56320 - 57343
// high-surrogates range, (\uD800-\uDBFF) = 55296 - 56319 for count != 0 {
// low-surrogates range, (\uDC00-\uDFFF) = 56320 - 57343 count--
var ch rune
if chars == nil {
ch = rune(random.Intn(gap) + start)
} else {
ch = chars[random.Intn(gap)+start]
}
for count != 0 { if letters && unicode.IsLetter(ch) || numbers && unicode.IsDigit(ch) || !letters && !numbers {
count-- if ch >= 56320 && ch <= 57343 { // low surrogate range
var ch rune if count == 0 {
if chars == nil { count++
ch = rune(random.Intn(gap) + start) } else {
} else { // Insert low surrogate
ch = chars[random.Intn(gap) + start] buffer[count] = ch
} count--
// Insert high surrogate
if letters && unicode.IsLetter(ch) || numbers && unicode.IsDigit(ch) || !letters && !numbers { buffer[count] = rune(55296 + random.Intn(128))
if ch >= 56320 && ch <= 57343 { // low surrogate range }
if count == 0 { } else if ch >= 55296 && ch <= 56191 { // High surrogates range (Partial)
count++ if count == 0 {
} else { count++
// Insert low surrogate } else {
buffer[count] = ch // Insert low surrogate
count-- buffer[count] = rune(56320 + random.Intn(128))
// Insert high surrogate count--
buffer[count] = rune(55296 + random.Intn(128)) // Insert high surrogate
} buffer[count] = ch
} else if ch >= 55296 && ch <= 56191 { // High surrogates range (Partial) }
if count == 0 { } else if ch >= 56192 && ch <= 56319 {
count++ // private high surrogate, skip it
} else { count++
// Insert low surrogate } else {
buffer[count] = rune(56320 + random.Intn(128)) // not one of the surrogates*
count-- buffer[count] = ch
// Insert high surrogate }
buffer[count] = ch } else {
} count++
} else if ch >= 56192 && ch <= 56319 { }
// private high surrogate, skip it }
count++ return string(buffer), nil
} else {
// not one of the surrogates*
buffer[count] = ch
}
} else {
count++
}
}
return string(buffer), nil
} }

View File

@ -1,85 +1,68 @@
package goutils package goutils
import ( import (
"testing" "fmt"
"fmt" "math/rand"
"math/rand" "testing"
) )
// ****************************** TESTS ******************************************** // ****************************** TESTS ********************************************
func TestRandomSeed(t *testing.T) { func TestRandomSeed(t *testing.T) {
// count, start, end, letters, numbers := 5, 0, 0, true, true // count, start, end, letters, numbers := 5, 0, 0, true, true
random := rand.New(rand.NewSource(10)) random := rand.New(rand.NewSource(10))
out := "3ip9v" out := "3ip9v"
// Test 1: Simulating RandomAlphaNumeric(count int) // Test 1: Simulating RandomAlphaNumeric(count int)
if x, _ := RandomSeed (5, 0, 0, true, true, nil, random); x != out { if x, _ := RandomSeed(5, 0, 0, true, true, nil, random); x != out {
t.Errorf("RandomSeed(%v, %v, %v, %v, %v, %v, %v) = %v, want %v", 5, 0, 0, true, true, nil, random, x, out) t.Errorf("RandomSeed(%v, %v, %v, %v, %v, %v, %v) = %v, want %v", 5, 0, 0, true, true, nil, random, x, out)
} }
// Test 2: Simulating RandomAlphabetic(count int) // Test 2: Simulating RandomAlphabetic(count int)
out = "MBrbj" out = "MBrbj"
if x, _ := RandomSeed (5, 0, 0, true, false, nil, random); x != out { if x, _ := RandomSeed(5, 0, 0, true, false, nil, random); x != out {
t.Errorf("RandomSeed(%v, %v, %v, %v, %v, %v, %v) = %v, want %v", 5, 0, 0, true, false, nil, random, x, out) t.Errorf("RandomSeed(%v, %v, %v, %v, %v, %v, %v) = %v, want %v", 5, 0, 0, true, false, nil, random, x, out)
} }
// Test 3: Simulating RandomNumeric(count int) // Test 3: Simulating RandomNumeric(count int)
out = "88935" out = "88935"
if x, _ := RandomSeed (5, 0, 0, false, true, nil, random); x != out { if x, _ := RandomSeed(5, 0, 0, false, true, nil, random); x != out {
t.Errorf("RandomSeed(%v, %v, %v, %v, %v, %v, %v) = %v, want %v", 5, 0, 0, false, true, nil, random, x, out) t.Errorf("RandomSeed(%v, %v, %v, %v, %v, %v, %v) = %v, want %v", 5, 0, 0, false, true, nil, random, x, out)
} }
// Test 4: Simulating RandomAscii(count int) // Test 4: Simulating RandomAscii(count int)
out = "H_I;E" out = "H_I;E"
if x, _ := RandomSeed (5, 32, 127, false, false, nil, random); x != out { if x, _ := RandomSeed(5, 32, 127, false, false, nil, random); x != out {
t.Errorf("RandomSeed(%v, %v, %v, %v, %v, %v, %v) = %v, want %v", 5, 32, 127, false, false, nil, random, x, out) t.Errorf("RandomSeed(%v, %v, %v, %v, %v, %v, %v) = %v, want %v", 5, 32, 127, false, false, nil, random, x, out)
} }
// Test 5: Simulating RandomSeed(...) with custom chars // Test 5: Simulating RandomSeed(...) with custom chars
chars := []rune {'1','2','3','a','b','c'} chars := []rune{'1', '2', '3', 'a', 'b', 'c'}
out = "2b2ca" out = "2b2ca"
if x, _ := RandomSeed (5, 0, 0, false, false, chars, random); x != out { if x, _ := RandomSeed(5, 0, 0, false, false, chars, random); x != out {
t.Errorf("RandomSeed(%v, %v, %v, %v, %v, %v, %v) = %v, want %v", 5, 0, 0, false, false, chars, random, x, out) t.Errorf("RandomSeed(%v, %v, %v, %v, %v, %v, %v) = %v, want %v", 5, 0, 0, false, false, chars, random, x, out)
} }
} }
// ****************************** EXAMPLES ******************************************** // ****************************** EXAMPLES ********************************************
func ExampleRandomSeed() { func ExampleRandomSeed() {
var seed int64 = 10 // If you change this seed #, the random sequence below will change var seed int64 = 10 // If you change this seed #, the random sequence below will change
random := rand.New(rand.NewSource(seed)) random := rand.New(rand.NewSource(seed))
chars := []rune {'1','2','3','a','b','c'} chars := []rune{'1', '2', '3', 'a', 'b', 'c'}
rand1, _ := RandomSeed (5, 0, 0, true, true, nil, random) // RandomAlphaNumeric (Alphabets and numbers possible) rand1, _ := RandomSeed(5, 0, 0, true, true, nil, random) // RandomAlphaNumeric (Alphabets and numbers possible)
rand2, _ := RandomSeed (5, 0, 0, true, false, nil, random) // RandomAlphabetic (Only alphabets) rand2, _ := RandomSeed(5, 0, 0, true, false, nil, random) // RandomAlphabetic (Only alphabets)
rand3, _ := RandomSeed (5, 0, 0, false, true, nil, random) // RandomNumeric (Only numbers) rand3, _ := RandomSeed(5, 0, 0, false, true, nil, random) // RandomNumeric (Only numbers)
rand4, _ := RandomSeed (5, 32, 127, false, false, nil, random) // RandomAscii (Alphabets, numbers, and other ASCII chars) rand4, _ := RandomSeed(5, 32, 127, false, false, nil, random) // RandomAscii (Alphabets, numbers, and other ASCII chars)
rand5, _ := RandomSeed (5, 0, 0, true, true, chars, random) // RandomSeed with custom characters rand5, _ := RandomSeed(5, 0, 0, true, true, chars, random) // RandomSeed with custom characters
fmt.Println(rand1) fmt.Println(rand1)
fmt.Println(rand2) fmt.Println(rand2)

View File

@ -16,18 +16,16 @@ limitations under the License.
package goutils package goutils
import ( import (
"fmt"
"unicode"
"bytes" "bytes"
"fmt"
"strings" "strings"
"unicode"
) )
// Typically returned by functions where a searched item cannot be found // Typically returned by functions where a searched item cannot be found
const INDEX_NOT_FOUND = -1 const INDEX_NOT_FOUND = -1
/* /*
Abbreviate abbreviates a string using ellipses. This will turn the string "Now is the time for all good men" into "Now is the time for..." Abbreviate abbreviates a string using ellipses. This will turn the string "Now is the time for all good men" into "Now is the time for..."
@ -46,11 +44,10 @@ Returns:
string - abbreviated string string - abbreviated string
error - if the width is too small error - if the width is too small
*/ */
func Abbreviate (str string, maxWidth int) (string, error) { func Abbreviate(str string, maxWidth int) (string, error) {
return AbbreviateFull(str, 0, maxWidth) return AbbreviateFull(str, 0, maxWidth)
} }
/* /*
AbbreviateFull abbreviates a string using ellipses. This will turn the string "Now is the time for all good men" into "...is the time for..." AbbreviateFull abbreviates a string using ellipses. This will turn the string "Now is the time for all good men" into "...is the time for..."
This function works like Abbreviate(string, int), but allows you to specify a "left edge" offset. Note that this left edge is not This function works like Abbreviate(string, int), but allows you to specify a "left edge" offset. Note that this left edge is not
@ -67,39 +64,38 @@ Returns:
string - abbreviated string string - abbreviated string
error - if the width is too small error - if the width is too small
*/ */
func AbbreviateFull (str string, offset int, maxWidth int) (string, error) { func AbbreviateFull(str string, offset int, maxWidth int) (string, error) {
if str == "" { if str == "" {
return "", nil return "", nil
} }
if maxWidth < 4 { if maxWidth < 4 {
err := fmt.Errorf("stringutils illegal argument: Minimum abbreviation width is 4") err := fmt.Errorf("stringutils illegal argument: Minimum abbreviation width is 4")
return "", err return "", err
} }
if len(str) <= maxWidth { if len(str) <= maxWidth {
return str, nil return str, nil
} }
if offset > len(str) { if offset > len(str) {
offset = len(str) offset = len(str)
} }
if len(str) - offset < (maxWidth - 3) { // 15 - 5 < 10 - 3 = 10 < 7 if len(str)-offset < (maxWidth - 3) { // 15 - 5 < 10 - 3 = 10 < 7
offset = len(str) - (maxWidth - 3) offset = len(str) - (maxWidth - 3)
} }
abrevMarker := "..." abrevMarker := "..."
if offset <= 4 { if offset <= 4 {
return str[0:maxWidth - 3] + abrevMarker, nil// str.substring(0, maxWidth - 3) + abrevMarker; return str[0:maxWidth-3] + abrevMarker, nil // str.substring(0, maxWidth - 3) + abrevMarker;
} }
if maxWidth < 7 { if maxWidth < 7 {
err := fmt.Errorf("stringutils illegal argument: Minimum abbreviation width with offset is 7") err := fmt.Errorf("stringutils illegal argument: Minimum abbreviation width with offset is 7")
return "", err return "", err
} }
if (offset + maxWidth - 3) < len(str) { // 5 + (10-3) < 15 = 12 < 15 if (offset + maxWidth - 3) < len(str) { // 5 + (10-3) < 15 = 12 < 15
abrevStr, _ := Abbreviate(str[offset:len(str)], (maxWidth - 3)) abrevStr, _ := Abbreviate(str[offset:len(str)], (maxWidth - 3))
return abrevMarker + abrevStr, nil// abrevMarker + abbreviate(str.substring(offset), maxWidth - 3); return abrevMarker + abrevStr, nil // abrevMarker + abbreviate(str.substring(offset), maxWidth - 3);
} }
return abrevMarker + str[(len(str) - (maxWidth - 3)):len(str)], nil // abrevMarker + str.substring(str.length() - (maxWidth - 3)); return abrevMarker + str[(len(str)-(maxWidth-3)):len(str)], nil // abrevMarker + str.substring(str.length() - (maxWidth - 3));
} }
/* /*
DeleteWhiteSpace deletes all whitespaces from a string as defined by unicode.IsSpace(rune). DeleteWhiteSpace deletes all whitespaces from a string as defined by unicode.IsSpace(rune).
It returns the string without whitespaces. It returns the string without whitespaces.
@ -111,26 +107,25 @@ Returns:
the string without whitespaces the string without whitespaces
*/ */
func DeleteWhiteSpace(str string) string { func DeleteWhiteSpace(str string) string {
if str == "" { if str == "" {
return str return str
} }
sz := len(str) sz := len(str)
var chs bytes.Buffer var chs bytes.Buffer
count := 0 count := 0
for i := 0; i < sz; i++ { for i := 0; i < sz; i++ {
ch := rune(str[i]) ch := rune(str[i])
if !unicode.IsSpace(ch) { if !unicode.IsSpace(ch) {
chs.WriteRune(ch) chs.WriteRune(ch)
count++ count++
} }
} }
if count == sz { if count == sz {
return str return str
} }
return chs.String() return chs.String()
} }
/* /*
IndexOfDifference compares two strings, and returns the index at which the strings begin to differ. IndexOfDifference compares two strings, and returns the index at which the strings begin to differ.
@ -142,26 +137,24 @@ Returns:
the index where str1 and str2 begin to differ; -1 if they are equal the index where str1 and str2 begin to differ; -1 if they are equal
*/ */
func IndexOfDifference(str1 string, str2 string) int { func IndexOfDifference(str1 string, str2 string) int {
if str1 == str2 { if str1 == str2 {
return INDEX_NOT_FOUND return INDEX_NOT_FOUND
} }
if IsEmpty(str1) || IsEmpty(str2) { if IsEmpty(str1) || IsEmpty(str2) {
return 0 return 0
} }
var i int; var i int
for i = 0; i < len(str1) && i < len(str2); i++ { for i = 0; i < len(str1) && i < len(str2); i++ {
if rune(str1[i]) != rune(str2[i]) { if rune(str1[i]) != rune(str2[i]) {
break break
} }
} }
if i < len(str2) || i < len(str1) { if i < len(str2) || i < len(str1) {
return i return i
} }
return INDEX_NOT_FOUND return INDEX_NOT_FOUND
} }
/* /*
IsBlank checks if a string is whitespace or empty (""). Observe the following behavior: IsBlank checks if a string is whitespace or empty (""). Observe the following behavior:
@ -177,19 +170,18 @@ Returns:
true - if the string is whitespace or empty ("") true - if the string is whitespace or empty ("")
*/ */
func IsBlank(str string) bool { func IsBlank(str string) bool {
strLen := len(str) strLen := len(str)
if str == "" || strLen == 0 { if str == "" || strLen == 0 {
return true return true
} }
for i := 0; i < strLen; i++ { for i := 0; i < strLen; i++ {
if unicode.IsSpace(rune(str[i])) == false { if unicode.IsSpace(rune(str[i])) == false {
return false return false
} }
} }
return true return true
} }
/* /*
IndexOf returns the index of the first instance of sub in str, with the search beginning from the IndexOf returns the index of the first instance of sub in str, with the search beginning from the
index start point specified. -1 is returned if sub is not present in str. index start point specified. -1 is returned if sub is not present in str.
@ -207,26 +199,26 @@ Returns:
*/ */
func IndexOf(str string, sub string, start int) int { func IndexOf(str string, sub string, start int) int {
if (start < 0) { if start < 0 {
start = 0 start = 0
} }
if len(str) < start { if len(str) < start {
return INDEX_NOT_FOUND return INDEX_NOT_FOUND
} }
if IsEmpty(str) || IsEmpty(sub) { if IsEmpty(str) || IsEmpty(sub) {
return INDEX_NOT_FOUND return INDEX_NOT_FOUND
} }
partialIndex := strings.Index(str[start:len(str)], sub) partialIndex := strings.Index(str[start:len(str)], sub)
if partialIndex == -1 { if partialIndex == -1 {
return INDEX_NOT_FOUND return INDEX_NOT_FOUND
} }
return partialIndex + start return partialIndex + start
} }
// IsEmpty checks if a string is empty (""). Returns true if empty, and false otherwise. // IsEmpty checks if a string is empty (""). Returns true if empty, and false otherwise.
func IsEmpty(str string) bool { func IsEmpty(str string) bool {
return len(str) == 0 return len(str) == 0
} }

View File

@ -1,8 +1,8 @@
package goutils package goutils
import ( import (
"testing" "fmt"
"fmt" "testing"
) )
// ****************************** TESTS ******************************************** // ****************************** TESTS ********************************************
@ -26,7 +26,6 @@ func TestAbbreviate(t *testing.T) {
t.Errorf("Abbreviate(%v, %v) = %v, want %v", in, maxWidth, x, out) t.Errorf("Abbreviate(%v, %v) = %v, want %v", in, maxWidth, x, out)
} }
// Test 3 // Test 3
out = "a..." out = "a..."
maxWidth = 4 maxWidth = 4
@ -36,7 +35,6 @@ func TestAbbreviate(t *testing.T) {
} }
} }
func TestAbbreviateFull(t *testing.T) { func TestAbbreviateFull(t *testing.T) {
// Test 1 // Test 1
@ -58,20 +56,16 @@ func TestAbbreviateFull(t *testing.T) {
t.Errorf("AbbreviateFull(%v, %v, %v) = %v, want %v", in, offset, maxWidth, x, out) t.Errorf("AbbreviateFull(%v, %v, %v) = %v, want %v", in, offset, maxWidth, x, out)
} }
// Test 3 // Test 3
out = "...ijklmno" out = "...ijklmno"
offset = 12 offset = 12
maxWidth = 10 maxWidth = 10
if x, _ := AbbreviateFull(in, offset, maxWidth); x != out { if x, _ := AbbreviateFull(in, offset, maxWidth); x != out {
t.Errorf("AbbreviateFull(%v, %v, %v) = %v, want %v", in, offset, maxWidth, x, out) t.Errorf("AbbreviateFull(%v, %v, %v) = %v, want %v", in, offset, maxWidth, x, out)
} }
} }
func TestIndexOf(t *testing.T) { func TestIndexOf(t *testing.T) {
// Test 1 // Test 1
@ -100,7 +94,6 @@ func TestIndexOf(t *testing.T) {
t.Errorf("IndexOf(%v, %v, %v) = %v, want %v", str, sub, start, x, out) t.Errorf("IndexOf(%v, %v, %v) = %v, want %v", str, sub, start, x, out)
} }
// Test 4 // Test 4
sub = "z" sub = "z"
out = -1 out = -1
@ -111,7 +104,6 @@ func TestIndexOf(t *testing.T) {
} }
func TestIsBlank(t *testing.T) { func TestIsBlank(t *testing.T) {
// Test 1 // Test 1
@ -139,8 +131,6 @@ func TestIsBlank(t *testing.T) {
} }
} }
func TestDeleteWhiteSpace(t *testing.T) { func TestDeleteWhiteSpace(t *testing.T) {
// Test 1 // Test 1
@ -160,7 +150,6 @@ func TestDeleteWhiteSpace(t *testing.T) {
} }
} }
func TestIndexOfDifference(t *testing.T) { func TestIndexOfDifference(t *testing.T) {
str1 := "abc" str1 := "abc"
@ -172,7 +161,6 @@ func TestIndexOfDifference(t *testing.T) {
} }
} }
// ****************************** EXAMPLES ******************************************** // ****************************** EXAMPLES ********************************************
func ExampleAbbreviate() { func ExampleAbbreviate() {
@ -184,7 +172,6 @@ func ExampleAbbreviate() {
out4, _ := Abbreviate(str, 4) out4, _ := Abbreviate(str, 4)
_, err1 := Abbreviate(str, 3) _, err1 := Abbreviate(str, 3)
fmt.Println(out1) fmt.Println(out1)
fmt.Println(out2) fmt.Println(out2)
fmt.Println(out3) fmt.Println(out3)
@ -198,7 +185,6 @@ func ExampleAbbreviate() {
// stringutils illegal argument: Minimum abbreviation width is 4 // stringutils illegal argument: Minimum abbreviation width is 4
} }
func ExampleAbbreviateFull() { func ExampleAbbreviateFull() {
str := "abcdefghijklmno" str := "abcdefghijklmno"
@ -215,7 +201,6 @@ func ExampleAbbreviateFull() {
_, err1 := AbbreviateFull(str2, 0, 3) _, err1 := AbbreviateFull(str2, 0, 3)
_, err2 := AbbreviateFull(str2, 5, 6) _, err2 := AbbreviateFull(str2, 5, 6)
fmt.Println(out1) fmt.Println(out1)
fmt.Println(out2) fmt.Println(out2)
fmt.Println(out3) fmt.Println(out3)
@ -241,7 +226,6 @@ func ExampleAbbreviateFull() {
// stringutils illegal argument: Minimum abbreviation width with offset is 7 // stringutils illegal argument: Minimum abbreviation width with offset is 7
} }
func ExampleIsBlank() { func ExampleIsBlank() {
out1 := IsBlank("") out1 := IsBlank("")
@ -260,7 +244,6 @@ func ExampleIsBlank() {
// false // false
} }
func ExampleDeleteWhiteSpace() { func ExampleDeleteWhiteSpace() {
out1 := DeleteWhiteSpace(" ") out1 := DeleteWhiteSpace(" ")
@ -279,7 +262,6 @@ func ExampleDeleteWhiteSpace() {
// bob // bob
} }
func ExampleIndexOf() { func ExampleIndexOf() {
str := "abcdefgehije" str := "abcdefgehije"
@ -308,8 +290,6 @@ func ExampleIndexOf() {
// -1 // -1
} }
func ExampleIndexOfDifference() { func ExampleIndexOfDifference() {
out1 := IndexOfDifference("abc", "abc") out1 := IndexOfDifference("abc", "abc")

View File

@ -68,8 +68,8 @@ Parameters:
Returns: Returns:
a line with newlines inserted a line with newlines inserted
*/ */
func Wrap (str string, wrapLength int) string { func Wrap(str string, wrapLength int) string {
return WrapCustom (str, wrapLength, "", false) return WrapCustom(str, wrapLength, "", false)
} }
/* /*
@ -85,7 +85,7 @@ Parameters:
Returns: Returns:
a line with newlines inserted a line with newlines inserted
*/ */
func WrapCustom (str string, wrapLength int, newLineStr string, wrapLongWords bool) string { func WrapCustom(str string, wrapLength int, newLineStr string, wrapLongWords bool) string {
if str == "" { if str == "" {
return "" return ""
@ -118,27 +118,27 @@ func WrapCustom (str string, wrapLength int, newLineStr string, wrapLongWords bo
wrappedLine.WriteString(newLineStr) wrappedLine.WriteString(newLineStr)
offset = spaceToWrapAt + 1 offset = spaceToWrapAt + 1
} else { } else {
// long word or URL // long word or URL
if wrapLongWords { if wrapLongWords {
end := wrapLength + offset end := wrapLength + offset
// long words are wrapped one line at a time // long words are wrapped one line at a time
wrappedLine.WriteString(str[offset:end]) wrappedLine.WriteString(str[offset:end])
wrappedLine.WriteString(newLineStr) wrappedLine.WriteString(newLineStr)
offset += wrapLength offset += wrapLength
} else { } else {
// long words aren't wrapped, just extended beyond limit // long words aren't wrapped, just extended beyond limit
end := wrapLength + offset end := wrapLength + offset
spaceToWrapAt = strings.IndexRune(str[end:len(str)], ' ') + end spaceToWrapAt = strings.IndexRune(str[end:len(str)], ' ') + end
if spaceToWrapAt >= 0 { if spaceToWrapAt >= 0 {
wrappedLine.WriteString(str[offset:spaceToWrapAt]) wrappedLine.WriteString(str[offset:spaceToWrapAt])
wrappedLine.WriteString(newLineStr) wrappedLine.WriteString(newLineStr)
offset = spaceToWrapAt + 1 offset = spaceToWrapAt + 1
} else { } else {
wrappedLine.WriteString(str[offset:len(str)]) wrappedLine.WriteString(str[offset:len(str)])
offset = inputLineLength offset = inputLineLength
} }
} }
} }
} }
@ -148,7 +148,6 @@ func WrapCustom (str string, wrapLength int, newLineStr string, wrapLongWords bo
} }
/* /*
Capitalize capitalizes all the delimiter separated words in a string. Only the first letter of each word is changed. Capitalize capitalizes all the delimiter separated words in a string. Only the first letter of each word is changed.
To convert the rest of each word to lowercase at the same time, use CapitalizeFully(str string, delimiters ...rune). To convert the rest of each word to lowercase at the same time, use CapitalizeFully(str string, delimiters ...rune).
@ -163,7 +162,7 @@ Parameters:
Returns: Returns:
capitalized string capitalized string
*/ */
func Capitalize (str string, delimiters ...rune) string { func Capitalize(str string, delimiters ...rune) string {
var delimLen int var delimLen int
@ -173,27 +172,25 @@ func Capitalize (str string, delimiters ...rune) string {
delimLen = len(delimiters) delimLen = len(delimiters)
} }
if str == "" || delimLen == 0 { if str == "" || delimLen == 0 {
return str; return str
} }
buffer := []rune(str) buffer := []rune(str)
capitalizeNext := true capitalizeNext := true
for i := 0; i < len(buffer); i++ { for i := 0; i < len(buffer); i++ {
ch := buffer[i] ch := buffer[i]
if isDelimiter(ch, delimiters...) { if isDelimiter(ch, delimiters...) {
capitalizeNext = true capitalizeNext = true
} else if capitalizeNext { } else if capitalizeNext {
buffer[i] = unicode.ToTitle(ch) buffer[i] = unicode.ToTitle(ch)
capitalizeNext = false capitalizeNext = false
} }
} }
return string(buffer) return string(buffer)
} }
/* /*
CapitalizeFully converts all the delimiter separated words in a string into capitalized words, that is each word is made up of a CapitalizeFully converts all the delimiter separated words in a string into capitalized words, that is each word is made up of a
titlecase character and then a series of lowercase characters. The delimiters represent a set of characters understood titlecase character and then a series of lowercase characters. The delimiters represent a set of characters understood
@ -207,25 +204,23 @@ Parameters:
Returns: Returns:
capitalized string capitalized string
*/ */
func CapitalizeFully (str string, delimiters ...rune) string { func CapitalizeFully(str string, delimiters ...rune) string {
var delimLen int var delimLen int
if delimiters == nil { if delimiters == nil {
delimLen = -1 delimLen = -1
} else { } else {
delimLen = len(delimiters) delimLen = len(delimiters)
} }
if str == "" || delimLen == 0 {
if str == "" || delimLen == 0 { return str
return str; }
} str = strings.ToLower(str)
str = strings.ToLower(str) return Capitalize(str, delimiters...)
return Capitalize(str, delimiters...);
} }
/* /*
Uncapitalize uncapitalizes all the whitespace separated words in a string. Only the first letter of each word is changed. Uncapitalize uncapitalizes all the whitespace separated words in a string. Only the first letter of each word is changed.
The delimiters represent a set of characters understood to separate words. The first string character and the first non-delimiter The delimiters represent a set of characters understood to separate words. The first string character and the first non-delimiter
@ -238,7 +233,7 @@ Parameters:
Returns: Returns:
uncapitalized string uncapitalized string
*/ */
func Uncapitalize (str string, delimiters ...rune) string { func Uncapitalize(str string, delimiters ...rune) string {
var delimLen int var delimLen int
@ -248,25 +243,24 @@ func Uncapitalize (str string, delimiters ...rune) string {
delimLen = len(delimiters) delimLen = len(delimiters)
} }
if str == "" || delimLen == 0 { if str == "" || delimLen == 0 {
return str; return str
} }
buffer := []rune(str) buffer := []rune(str)
uncapitalizeNext := true // TODO Always makes capitalize/un apply to first char. uncapitalizeNext := true // TODO Always makes capitalize/un apply to first char.
for i := 0; i < len(buffer); i++ { for i := 0; i < len(buffer); i++ {
ch := buffer[i] ch := buffer[i]
if isDelimiter(ch, delimiters...) { if isDelimiter(ch, delimiters...) {
uncapitalizeNext = true uncapitalizeNext = true
} else if uncapitalizeNext { } else if uncapitalizeNext {
buffer[i] = unicode.ToLower(ch) buffer[i] = unicode.ToLower(ch)
uncapitalizeNext = false uncapitalizeNext = false
} }
} }
return string(buffer) return string(buffer)
} }
/* /*
SwapCase swaps the case of a string using a word based algorithm. SwapCase swaps the case of a string using a word based algorithm.
@ -285,36 +279,35 @@ Returns:
the changed string the changed string
*/ */
func SwapCase(str string) string { func SwapCase(str string) string {
if str == "" { if str == "" {
return str return str
} }
buffer := []rune(str) buffer := []rune(str)
whitespace := true whitespace := true
for i := 0; i < len(buffer); i++ { for i := 0; i < len(buffer); i++ {
ch := buffer[i] ch := buffer[i]
if unicode.IsUpper(ch) { if unicode.IsUpper(ch) {
buffer[i] = unicode.ToLower(ch) buffer[i] = unicode.ToLower(ch)
whitespace = false whitespace = false
} else if unicode.IsTitle(ch) { } else if unicode.IsTitle(ch) {
buffer[i] = unicode.ToLower(ch) buffer[i] = unicode.ToLower(ch)
whitespace = false whitespace = false
} else if unicode.IsLower(ch) { } else if unicode.IsLower(ch) {
if whitespace { if whitespace {
buffer[i] = unicode.ToTitle(ch) buffer[i] = unicode.ToTitle(ch)
whitespace = false whitespace = false
} else { } else {
buffer[i] = unicode.ToUpper(ch) buffer[i] = unicode.ToUpper(ch)
} }
} else { } else {
whitespace = unicode.IsSpace(ch) whitespace = unicode.IsSpace(ch)
} }
} }
return string(buffer); return string(buffer)
} }
/* /*
Initials extracts the initial letters from each word in the string. The first letter of the string and all first Initials extracts the initial letters from each word in the string. The first letter of the string and all first
letters after the defined delimiters are returned as a new string. Their case is not changed. If the delimiters letters after the defined delimiters are returned as a new string. Their case is not changed. If the delimiters
@ -327,39 +320,37 @@ Returns:
string of initial letters string of initial letters
*/ */
func Initials(str string, delimiters ...rune) string { func Initials(str string, delimiters ...rune) string {
if str == "" { if str == "" {
return str return str
} }
if delimiters != nil && len(delimiters) == 0 { if delimiters != nil && len(delimiters) == 0 {
return "" return ""
} }
strLen := len(str) strLen := len(str)
var buf bytes.Buffer var buf bytes.Buffer
lastWasGap := true lastWasGap := true
for i := 0; i < strLen; i++ { for i := 0; i < strLen; i++ {
ch := rune(str[i]) ch := rune(str[i])
if isDelimiter(ch, delimiters...) { if isDelimiter(ch, delimiters...) {
lastWasGap = true lastWasGap = true
} else if lastWasGap { } else if lastWasGap {
buf.WriteRune(ch) buf.WriteRune(ch)
lastWasGap = false lastWasGap = false
} }
} }
return buf.String() return buf.String()
} }
// private function (lower case func name)
func isDelimiter(ch rune, delimiters ...rune) bool {
// private function (lower case func name) if delimiters == nil {
func isDelimiter(ch rune, delimiters ...rune) bool { return unicode.IsSpace(ch)
if delimiters == nil { }
return unicode.IsSpace(ch) for _, delimiter := range delimiters {
} if ch == delimiter {
for _, delimiter := range delimiters { return true
if ch == delimiter { }
return true }
} return false
}
return false
} }

View File

@ -1,8 +1,8 @@
package goutils package goutils
import ( import (
"testing" "fmt"
"fmt" "testing"
) )
// ****************************** TESTS ******************************************** // ****************************** TESTS ********************************************
@ -18,7 +18,6 @@ func TestWrapNormalWord(t *testing.T) {
} }
} }
func TestWrapCustomLongWordFalse(t *testing.T) { func TestWrapCustomLongWordFalse(t *testing.T) {
in := "BobManuelBob Bob" in := "BobManuelBob Bob"
@ -32,7 +31,6 @@ func TestWrapCustomLongWordFalse(t *testing.T) {
} }
} }
func TestWrapCustomLongWordTrue(t *testing.T) { func TestWrapCustomLongWordTrue(t *testing.T) {
in := "BobManuelBob Bob" in := "BobManuelBob Bob"
@ -46,10 +44,8 @@ func TestWrapCustomLongWordTrue(t *testing.T) {
} }
} }
func TestCapitalize(t *testing.T) { func TestCapitalize(t *testing.T) {
// Test 1: Checks if function works with 1 parameter, and default whitespace delimiter // Test 1: Checks if function works with 1 parameter, and default whitespace delimiter
in := "test is going.well.thank.you.for inquiring" in := "test is going.well.thank.you.for inquiring"
out := "Test Is Going.well.thank.you.for Inquiring" out := "Test Is Going.well.thank.you.for Inquiring"
@ -58,7 +54,6 @@ func TestCapitalize(t *testing.T) {
t.Errorf("Capitalize(%v) = %v, want %v", in, x, out) t.Errorf("Capitalize(%v) = %v, want %v", in, x, out)
} }
// Test 2: Checks if function works with both parameters, with param 2 containing whitespace and '.' // Test 2: Checks if function works with both parameters, with param 2 containing whitespace and '.'
out = "Test Is Going.Well.Thank.You.For Inquiring" out = "Test Is Going.Well.Thank.You.For Inquiring"
delimiters := []rune{' ', '.'} delimiters := []rune{' ', '.'}
@ -68,8 +63,6 @@ func TestCapitalize(t *testing.T) {
} }
} }
func TestCapitalizeFully(t *testing.T) { func TestCapitalizeFully(t *testing.T) {
// Test 1 // Test 1
@ -80,7 +73,6 @@ func TestCapitalizeFully(t *testing.T) {
t.Errorf("CapitalizeFully(%v) = %v, want %v", in, x, out) t.Errorf("CapitalizeFully(%v) = %v, want %v", in, x, out)
} }
// Test 2 // Test 2
out = "Test Is Going.Well.Thank.You.For Inquiring" out = "Test Is Going.Well.Thank.You.For Inquiring"
delimiters := []rune{' ', '.'} delimiters := []rune{' ', '.'}
@ -90,7 +82,6 @@ func TestCapitalizeFully(t *testing.T) {
} }
} }
func TestUncapitalize(t *testing.T) { func TestUncapitalize(t *testing.T) {
// Test 1: Checks if function works with 1 parameter, and default whitespace delimiter // Test 1: Checks if function works with 1 parameter, and default whitespace delimiter
@ -110,7 +101,6 @@ func TestUncapitalize(t *testing.T) {
} }
} }
func TestSwapCase(t *testing.T) { func TestSwapCase(t *testing.T) {
in := "This Is A.Test" in := "This Is A.Test"
@ -121,7 +111,6 @@ func TestSwapCase(t *testing.T) {
} }
} }
func TestInitials(t *testing.T) { func TestInitials(t *testing.T) {
// Test 1 // Test 1
@ -132,10 +121,9 @@ func TestInitials(t *testing.T) {
t.Errorf("Initials(%v) = %v, want %v", in, x, out) t.Errorf("Initials(%v) = %v, want %v", in, x, out)
} }
// Test 2 // Test 2
out = "JDR" out = "JDR"
delimiters := []rune{' ','.'} delimiters := []rune{' ', '.'}
if x := Initials(in, delimiters...); x != out { if x := Initials(in, delimiters...); x != out {
t.Errorf("Initials(%v) = %v, want %v", in, x, out) t.Errorf("Initials(%v) = %v, want %v", in, x, out)
@ -143,10 +131,6 @@ func TestInitials(t *testing.T) {
} }
// ****************************** EXAMPLES ******************************************** // ****************************** EXAMPLES ********************************************
func ExampleWrap() { func ExampleWrap() {
@ -154,13 +138,12 @@ func ExampleWrap() {
in := "Bob Manuel Bob Manuel" in := "Bob Manuel Bob Manuel"
wrapLength := 10 wrapLength := 10
fmt.Println (Wrap(in, wrapLength)) fmt.Println(Wrap(in, wrapLength))
// Output: // Output:
// Bob Manuel // Bob Manuel
// Bob Manuel // Bob Manuel
} }
func ExampleWrapCustom_1() { func ExampleWrapCustom_1() {
in := "BobManuelBob Bob" in := "BobManuelBob Bob"
@ -168,12 +151,11 @@ func ExampleWrapCustom_1() {
newLineStr := "<br\\>" newLineStr := "<br\\>"
wrapLongWords := false wrapLongWords := false
fmt.Println (WrapCustom(in, wrapLength, newLineStr, wrapLongWords)) fmt.Println(WrapCustom(in, wrapLength, newLineStr, wrapLongWords))
// Output: // Output:
// BobManuelBob<br\>Bob // BobManuelBob<br\>Bob
} }
func ExampleWrapCustom_2() { func ExampleWrapCustom_2() {
in := "BobManuelBob Bob" in := "BobManuelBob Bob"
@ -181,72 +163,63 @@ func ExampleWrapCustom_2() {
newLineStr := "<br\\>" newLineStr := "<br\\>"
wrapLongWords := true wrapLongWords := true
fmt.Println (WrapCustom(in, wrapLength, newLineStr, wrapLongWords)) fmt.Println(WrapCustom(in, wrapLength, newLineStr, wrapLongWords))
// Output: // Output:
// BobManuelB<br\>ob Bob // BobManuelB<br\>ob Bob
} }
func ExampleCapitalize() { func ExampleCapitalize() {
in := "test is going.well.thank.you.for inquiring" // Compare input to CapitalizeFully example in := "test is going.well.thank.you.for inquiring" // Compare input to CapitalizeFully example
delimiters := []rune{' ', '.'} delimiters := []rune{' ', '.'}
fmt.Println (Capitalize(in)) fmt.Println(Capitalize(in))
fmt.Println (Capitalize(in, delimiters...)) fmt.Println(Capitalize(in, delimiters...))
// Output: // Output:
// Test Is Going.well.thank.you.for Inquiring // Test Is Going.well.thank.you.for Inquiring
// Test Is Going.Well.Thank.You.For Inquiring // Test Is Going.Well.Thank.You.For Inquiring
} }
func ExampleCapitalizeFully() { func ExampleCapitalizeFully() {
in := "tEsT iS goiNG.wELL.tHaNk.yOU.for inqUIrING" // Notice scattered capitalization
in := "tEsT iS goiNG.wELL.tHaNk.yOU.for inqUIrING" // Notice scattered capitalization
delimiters := []rune{' ', '.'} delimiters := []rune{' ', '.'}
fmt.Println (CapitalizeFully(in)) fmt.Println(CapitalizeFully(in))
fmt.Println (CapitalizeFully(in, delimiters...)) fmt.Println(CapitalizeFully(in, delimiters...))
// Output: // Output:
// Test Is Going.well.thank.you.for Inquiring // Test Is Going.well.thank.you.for Inquiring
// Test Is Going.Well.Thank.You.For Inquiring // Test Is Going.Well.Thank.You.For Inquiring
} }
func ExampleUncapitalize() { func ExampleUncapitalize() {
in := "This Is A.Test" in := "This Is A.Test"
delimiters := []rune{' ', '.'} delimiters := []rune{' ', '.'}
fmt.Println (Uncapitalize(in)) fmt.Println(Uncapitalize(in))
fmt.Println (Uncapitalize(in, delimiters...)) fmt.Println(Uncapitalize(in, delimiters...))
// Output: // Output:
// this is a.Test // this is a.Test
// this is a.test // this is a.test
} }
func ExampleSwapCase() { func ExampleSwapCase() {
in := "This Is A.Test" in := "This Is A.Test"
fmt.Println (SwapCase(in)) fmt.Println(SwapCase(in))
// Output: // Output:
// tHIS iS a.tEST // tHIS iS a.tEST
} }
func ExampleInitials() { func ExampleInitials() {
in := "John Doe.Ray" in := "John Doe.Ray"
delimiters := []rune{' ','.'} delimiters := []rune{' ', '.'}
fmt.Println (Initials(in)) fmt.Println(Initials(in))
fmt.Println (Initials(in, delimiters...)) fmt.Println(Initials(in, delimiters...))
// Output: // Output:
// JD // JD
// JDR // JDR
} }

View File

@ -25,8 +25,8 @@ This library supports the parsing and verification as well as the generation and
See [the project documentation](https://godoc.org/github.com/dgrijalva/jwt-go) for examples of usage: See [the project documentation](https://godoc.org/github.com/dgrijalva/jwt-go) for examples of usage:
* [Simple example of parsing and validating a token](https://godoc.org/github.com/dgrijalva/jwt-go#example_Parse_hmac) * [Simple example of parsing and validating a token](https://godoc.org/github.com/dgrijalva/jwt-go#example-Parse--Hmac)
* [Simple example of building and signing a token](https://godoc.org/github.com/dgrijalva/jwt-go#example_New_hmac) * [Simple example of building and signing a token](https://godoc.org/github.com/dgrijalva/jwt-go#example-New--Hmac)
* [Directory of Examples](https://godoc.org/github.com/dgrijalva/jwt-go#pkg-examples) * [Directory of Examples](https://godoc.org/github.com/dgrijalva/jwt-go#pkg-examples)
## Extensions ## Extensions

View File

@ -8,8 +8,9 @@ import (
) )
type Parser struct { type Parser struct {
ValidMethods []string // If populated, only these methods will be considered valid ValidMethods []string // If populated, only these methods will be considered valid
UseJSONNumber bool // Use JSON Number format in JSON decoder UseJSONNumber bool // Use JSON Number format in JSON decoder
SkipClaimsValidation bool // Skip claims validation during token parsing
} }
// Parse, validate, and return a token. // Parse, validate, and return a token.
@ -101,14 +102,16 @@ func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyf
vErr := &ValidationError{} vErr := &ValidationError{}
// Validate Claims // Validate Claims
if err := token.Claims.Valid(); err != nil { if !p.SkipClaimsValidation {
if err := token.Claims.Valid(); err != nil {
// If the Claims Valid returned an error, check if it is a validation error, // If the Claims Valid returned an error, check if it is a validation error,
// If it was another error type, create a ValidationError with a generic ClaimsInvalid flag set // If it was another error type, create a ValidationError with a generic ClaimsInvalid flag set
if e, ok := err.(*ValidationError); !ok { if e, ok := err.(*ValidationError); !ok {
vErr = &ValidationError{Inner: err, Errors: ValidationErrorClaimsInvalid} vErr = &ValidationError{Inner: err, Errors: ValidationErrorClaimsInvalid}
} else { } else {
vErr = e vErr = e
}
} }
} }

View File

@ -172,6 +172,15 @@ var jwtTestData = []struct {
jwt.ValidationErrorNotValidYet | jwt.ValidationErrorExpired, jwt.ValidationErrorNotValidYet | jwt.ValidationErrorExpired,
&jwt.Parser{UseJSONNumber: true}, &jwt.Parser{UseJSONNumber: true},
}, },
{
"SkipClaimsValidation during token parsing",
"", // autogen
defaultKeyFunc,
jwt.MapClaims{"foo": "bar", "nbf": json.Number(fmt.Sprintf("%v", time.Now().Unix()+100))},
true,
0,
&jwt.Parser{UseJSONNumber: true, SkipClaimsValidation: true},
},
} }
func TestParser_Parse(t *testing.T) { func TestParser_Parse(t *testing.T) {

View File

@ -5,7 +5,7 @@ examples/profilesvc/profilesvc
examples/stringsvc1/stringsvc1 examples/stringsvc1/stringsvc1
examples/stringsvc2/stringsvc2 examples/stringsvc2/stringsvc2
examples/stringsvc3/stringsvc3 examples/stringsvc3/stringsvc3
*.coverprofile gover.coverprofile
# Compiled Object files, Static and Dynamic libs (Shared Objects) # Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o *.o

View File

@ -3,6 +3,7 @@ language: go
script: go test -race -v ./... script: go test -race -v ./...
go: go:
- 1.7.5 - 1.5.4
- 1.8 - 1.6.3
- 1.7.1
- tip - tip

View File

@ -1,4 +1,4 @@
# Go kit [![Circle CI](https://circleci.com/gh/go-kit/kit.svg?style=svg)](https://circleci.com/gh/go-kit/kit) [![Travis CI](https://travis-ci.org/go-kit/kit.svg?branch=master)](https://travis-ci.org/go-kit/kit) [![GoDoc](https://godoc.org/github.com/go-kit/kit?status.svg)](https://godoc.org/github.com/go-kit/kit) [![Coverage Status](https://coveralls.io/repos/go-kit/kit/badge.svg?branch=master&service=github)](https://coveralls.io/github/go-kit/kit?branch=master) [![Go Report Card](https://goreportcard.com/badge/go-kit/kit)](https://goreportcard.com/report/go-kit/kit) [![Sourcegraph](https://sourcegraph.com/github.com/go-kit/kit/-/badge.svg)](https://sourcegraph.com/github.com/go-kit/kit?badge) # Go kit [![Circle CI](https://circleci.com/gh/go-kit/kit.svg?style=svg)](https://circleci.com/gh/go-kit/kit) [![Drone.io](https://drone.io/github.com/go-kit/kit/status.png)](https://drone.io/github.com/go-kit/kit/latest) [![Travis CI](https://travis-ci.org/go-kit/kit.svg?branch=master)](https://travis-ci.org/go-kit/kit) [![GoDoc](https://godoc.org/github.com/go-kit/kit?status.svg)](https://godoc.org/github.com/go-kit/kit) [![Coverage Status](https://coveralls.io/repos/go-kit/kit/badge.svg?branch=master&service=github)](https://coveralls.io/github/go-kit/kit?branch=master) [![Go Report Card](https://goreportcard.com/badge/go-kit/kit)](https://goreportcard.com/report/go-kit/kit)
**Go kit** is a **distributed programming toolkit** for building microservices **Go kit** is a **distributed programming toolkit** for building microservices
in large organizations. We solve common problems in distributed systems, so in large organizations. We solve common problems in distributed systems, so
@ -108,14 +108,10 @@ Projects with a ★ have had particular influence on Go kit's design (or vice-ve
- [Goji](https://github.com/zenazn/goji) - [Goji](https://github.com/zenazn/goji)
- [Martini](https://github.com/go-martini/martini) - [Martini](https://github.com/go-martini/martini)
- [Beego](http://beego.me/) - [Beego](http://beego.me/)
- [Revel](https://revel.github.io/) (considered [harmful](https://github.com/go-kit/kit/issues/350)) - [Revel](https://revel.github.io/) (considered harmful)
## Additional reading ## Additional reading
- [Architecting for the Cloud](http://fr.slideshare.net/stonse/architecting-for-the-cloud-using-netflixoss-codemash-workshop-29852233) — Netflix - [Architecting for the Cloud](http://fr.slideshare.net/stonse/architecting-for-the-cloud-using-netflixoss-codemash-workshop-29852233) — Netflix
- [Dapper, a Large-Scale Distributed Systems Tracing Infrastructure](http://research.google.com/pubs/pub36356.html) — Google - [Dapper, a Large-Scale Distributed Systems Tracing Infrastructure](http://research.google.com/pubs/pub36356.html) — Google
- [Your Server as a Function](http://monkey.org/~marius/funsrv.pdf) (PDF) — Twitter - [Your Server as a Function](http://monkey.org/~marius/funsrv.pdf) (PDF) — Twitter
---
Development supported by [DigitalOcean](https://digitalocean.com).

View File

@ -92,7 +92,7 @@ Example of use in a server:
```go ```go
import ( import (
"context" "golang.org/x/net/context"
"github.com/go-kit/kit/auth/jwt" "github.com/go-kit/kit/auth/jwt"
"github.com/go-kit/kit/log" "github.com/go-kit/kit/log"

View File

@ -1,10 +1,10 @@
package jwt package jwt
import ( import (
"context"
"errors" "errors"
jwt "github.com/dgrijalva/jwt-go" jwt "github.com/dgrijalva/jwt-go"
"golang.org/x/net/context"
"github.com/go-kit/kit/endpoint" "github.com/go-kit/kit/endpoint"
) )
@ -15,8 +15,7 @@ const (
// JWTTokenContextKey holds the key used to store a JWT Token in the // JWTTokenContextKey holds the key used to store a JWT Token in the
// context. // context.
JWTTokenContextKey contextKey = "JWTToken" JWTTokenContextKey contextKey = "JWTToken"
// JWTClaimsContxtKey holds the key used to store the JWT Claims in the
// JWTClaimsContextKey holds the key used to store the JWT Claims in the
// context. // context.
JWTClaimsContextKey contextKey = "JWTClaims" JWTClaimsContextKey contextKey = "JWTClaims"
) )
@ -25,26 +24,20 @@ var (
// ErrTokenContextMissing denotes a token was not passed into the parsing // ErrTokenContextMissing denotes a token was not passed into the parsing
// middleware's context. // middleware's context.
ErrTokenContextMissing = errors.New("token up for parsing was not passed through the context") ErrTokenContextMissing = errors.New("token up for parsing was not passed through the context")
// ErrTokenInvalid denotes a token was not able to be validated. // ErrTokenInvalid denotes a token was not able to be validated.
ErrTokenInvalid = errors.New("JWT Token was invalid") ErrTokenInvalid = errors.New("JWT Token was invalid")
// ErrTokenExpired denotes a token's expire header (exp) has since passed. // ErrTokenExpired denotes a token's expire header (exp) has since passed.
ErrTokenExpired = errors.New("JWT Token is expired") ErrTokenExpired = errors.New("JWT Token is expired")
// ErrTokenMalformed denotes a token was not formatted as a JWT token. // ErrTokenMalformed denotes a token was not formatted as a JWT token.
ErrTokenMalformed = errors.New("JWT Token is malformed") ErrTokenMalformed = errors.New("JWT Token is malformed")
// ErrTokenNotActive denotes a token's not before header (nbf) is in the // ErrTokenNotActive denotes a token's not before header (nbf) is in the
// future. // future.
ErrTokenNotActive = errors.New("token is not valid yet") ErrTokenNotActive = errors.New("token is not valid yet")
// ErrUncesptedSigningMethod denotes a token was signed with an unexpected
// ErrUnexpectedSigningMethod denotes a token was signed with an unexpected
// signing method. // signing method.
ErrUnexpectedSigningMethod = errors.New("unexpected signing method") ErrUnexpectedSigningMethod = errors.New("unexpected signing method")
) )
// Claims is a map of arbitrary claim data.
type Claims map[string]interface{} type Claims map[string]interface{}
// NewSigner creates a new JWT token generating middleware, specifying key ID, // NewSigner creates a new JWT token generating middleware, specifying key ID,

View File

@ -1,10 +1,11 @@
package jwt package jwt
import ( import (
"context"
"testing" "testing"
jwt "github.com/dgrijalva/jwt-go" jwt "github.com/dgrijalva/jwt-go"
"golang.org/x/net/context"
) )
var ( var (

View File

@ -1,11 +1,11 @@
package jwt package jwt
import ( import (
"context"
"fmt" "fmt"
stdhttp "net/http" stdhttp "net/http"
"strings" "strings"
"golang.org/x/net/context"
"google.golang.org/grpc/metadata" "google.golang.org/grpc/metadata"
"github.com/go-kit/kit/transport/grpc" "github.com/go-kit/kit/transport/grpc"
@ -44,10 +44,10 @@ func FromHTTPContext() http.RequestFunc {
// ToGRPCContext moves JWT token from grpc metadata to context. Particularly // ToGRPCContext moves JWT token from grpc metadata to context. Particularly
// userful for servers. // userful for servers.
func ToGRPCContext() grpc.ServerRequestFunc { func ToGRPCContext() grpc.RequestFunc {
return func(ctx context.Context, md metadata.MD) context.Context { return func(ctx context.Context, md *metadata.MD) context.Context {
// capital "Key" is illegal in HTTP/2. // capital "Key" is illegal in HTTP/2.
authHeader, ok := md["authorization"] authHeader, ok := (*md)["authorization"]
if !ok { if !ok {
return ctx return ctx
} }
@ -63,7 +63,7 @@ func ToGRPCContext() grpc.ServerRequestFunc {
// FromGRPCContext moves JWT token from context to grpc metadata. Particularly // FromGRPCContext moves JWT token from context to grpc metadata. Particularly
// useful for clients. // useful for clients.
func FromGRPCContext() grpc.ClientRequestFunc { func FromGRPCContext() grpc.RequestFunc {
return func(ctx context.Context, md *metadata.MD) context.Context { return func(ctx context.Context, md *metadata.MD) context.Context {
token, ok := ctx.Value(JWTTokenContextKey).(string) token, ok := ctx.Value(JWTTokenContextKey).(string)
if ok { if ok {

View File

@ -1,12 +1,13 @@
package jwt package jwt
import ( import (
"context"
"fmt" "fmt"
"net/http" "net/http"
"testing" "testing"
"google.golang.org/grpc/metadata" "google.golang.org/grpc/metadata"
"golang.org/x/net/context"
) )
func TestToHTTPContext(t *testing.T) { func TestToHTTPContext(t *testing.T) {
@ -69,7 +70,7 @@ func TestToGRPCContext(t *testing.T) {
reqFunc := ToGRPCContext() reqFunc := ToGRPCContext()
// No Authorization header is passed // No Authorization header is passed
ctx := reqFunc(context.Background(), md) ctx := reqFunc(context.Background(), &md)
token := ctx.Value(JWTTokenContextKey) token := ctx.Value(JWTTokenContextKey)
if token != nil { if token != nil {
t.Error("Context should not contain a JWT Token") t.Error("Context should not contain a JWT Token")
@ -77,7 +78,7 @@ func TestToGRPCContext(t *testing.T) {
// Invalid Authorization header is passed // Invalid Authorization header is passed
md["authorization"] = []string{fmt.Sprintf("%s", signedKey)} md["authorization"] = []string{fmt.Sprintf("%s", signedKey)}
ctx = reqFunc(context.Background(), md) ctx = reqFunc(context.Background(), &md)
token = ctx.Value(JWTTokenContextKey) token = ctx.Value(JWTTokenContextKey)
if token != nil { if token != nil {
t.Error("Context should not contain a JWT Token") t.Error("Context should not contain a JWT Token")
@ -85,7 +86,7 @@ func TestToGRPCContext(t *testing.T) {
// Authorization header is correct // Authorization header is correct
md["authorization"] = []string{fmt.Sprintf("Bearer %s", signedKey)} md["authorization"] = []string{fmt.Sprintf("Bearer %s", signedKey)}
ctx = reqFunc(context.Background(), md) ctx = reqFunc(context.Background(), &md)
token, ok := ctx.Value(JWTTokenContextKey).(string) token, ok := ctx.Value(JWTTokenContextKey).(string)
if !ok { if !ok {
t.Fatal("JWT Token not passed to context correctly") t.Fatal("JWT Token not passed to context correctly")

View File

@ -1,26 +0,0 @@
machine:
pre:
- curl -sSL https://s3.amazonaws.com/circle-downloads/install-circleci-docker.sh | bash -s -- 1.10.0
- sudo rm -rf /usr/local/go
- curl -sSL https://storage.googleapis.com/golang/go1.8.linux-amd64.tar.gz | sudo tar xz -C /usr/local
services:
- docker
dependencies:
pre:
- sudo curl -L "https://github.com/docker/compose/releases/download/1.10.0/docker-compose-linux-x86_64" -o /usr/local/bin/docker-compose
- sudo chmod +x /usr/local/bin/docker-compose
- docker-compose -f docker-compose-integration.yml up -d --force-recreate
test:
pre:
- mkdir -p /home/ubuntu/.go_workspace/src/github.com/go-kit
- mv /home/ubuntu/kit /home/ubuntu/.go_workspace/src/github.com/go-kit
- ln -s /home/ubuntu/.go_workspace/src/github.com/go-kit/kit /home/ubuntu/kit
- go get github.com/go-kit/kit/...
override:
- go test -v -race -tags integration github.com/go-kit/kit/...:
environment:
ETCD_ADDR: http://localhost:2379
CONSUL_ADDR: localhost:8500
ZK_ADDR: localhost:2181

View File

@ -1,9 +1,8 @@
package circuitbreaker package circuitbreaker
import ( import (
"context"
"github.com/sony/gobreaker" "github.com/sony/gobreaker"
"golang.org/x/net/context"
"github.com/go-kit/kit/endpoint" "github.com/go-kit/kit/endpoint"
) )

View File

@ -1,10 +1,10 @@
package circuitbreaker package circuitbreaker
import ( import (
"context"
"time" "time"
"github.com/streadway/handy/breaker" "github.com/streadway/handy/breaker"
"golang.org/x/net/context"
"github.com/go-kit/kit/endpoint" "github.com/go-kit/kit/endpoint"
) )

View File

@ -1,9 +1,8 @@
package circuitbreaker package circuitbreaker
import ( import (
"context"
"github.com/afex/hystrix-go/hystrix" "github.com/afex/hystrix-go/hystrix"
"golang.org/x/net/context"
"github.com/go-kit/kit/endpoint" "github.com/go-kit/kit/endpoint"
) )

View File

@ -1,7 +1,6 @@
package circuitbreaker_test package circuitbreaker_test
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"path/filepath" "path/filepath"
@ -9,6 +8,8 @@ import (
"testing" "testing"
"time" "time"
"golang.org/x/net/context"
"github.com/go-kit/kit/endpoint" "github.com/go-kit/kit/endpoint"
) )
@ -66,7 +67,7 @@ func testFailingEndpoint(
type mock struct { type mock struct {
through int through int
err error err error
} }
func (m *mock) endpoint(context.Context, interface{}) (interface{}, error) { func (m *mock) endpoint(context.Context, interface{}) (interface{}, error) {

View File

@ -1,16 +0,0 @@
version: '2'
services:
etcd:
image: quay.io/coreos/etcd
ports:
- "2379:2379"
command: /usr/local/bin/etcd -advertise-client-urls http://0.0.0.0:2379,http://0.0.0.0:4001 -listen-client-urls "http://0.0.0.0:2379,http://0.0.0.0:4001"
consul:
image: progrium/consul
ports:
- "8500:8500"
command: -server -bootstrap
zk:
image: zookeeper
ports:
- "2181:2181"

View File

@ -1,7 +1,7 @@
package endpoint package endpoint
import ( import (
"context" "golang.org/x/net/context"
) )
// Endpoint is the fundamental building block of servers and clients. // Endpoint is the fundamental building block of servers and clients.

View File

@ -1,9 +1,10 @@
package endpoint_test package endpoint_test
import ( import (
"context"
"fmt" "fmt"
"golang.org/x/net/context"
"github.com/go-kit/kit/endpoint" "github.com/go-kit/kit/endpoint"
) )

View File

@ -67,10 +67,10 @@ func New(instance string, tracer stdopentracing.Tracer, logger log.Logger) (adds
).Endpoint() ).Endpoint()
concatEndpoint = opentracing.TraceClient(tracer, "Concat")(concatEndpoint) concatEndpoint = opentracing.TraceClient(tracer, "Concat")(concatEndpoint)
concatEndpoint = limiter(concatEndpoint) concatEndpoint = limiter(concatEndpoint)
concatEndpoint = circuitbreaker.Gobreaker(gobreaker.NewCircuitBreaker(gobreaker.Settings{ sumEndpoint = circuitbreaker.Gobreaker(gobreaker.NewCircuitBreaker(gobreaker.Settings{
Name: "Concat", Name: "Concat",
Timeout: 30 * time.Second, Timeout: 30 * time.Second,
}))(concatEndpoint) }))(sumEndpoint)
} }
return addsvc.Endpoints{ return addsvc.Endpoints{

View File

@ -1,7 +1,6 @@
package main package main
import ( import (
"context"
"flag" "flag"
"fmt" "fmt"
"os" "os"
@ -13,6 +12,7 @@ import (
"github.com/lightstep/lightstep-tracer-go" "github.com/lightstep/lightstep-tracer-go"
stdopentracing "github.com/opentracing/opentracing-go" stdopentracing "github.com/opentracing/opentracing-go"
zipkin "github.com/openzipkin/zipkin-go-opentracing" zipkin "github.com/openzipkin/zipkin-go-opentracing"
"golang.org/x/net/context"
"google.golang.org/grpc" "google.golang.org/grpc"
"sourcegraph.com/sourcegraph/appdash" "sourcegraph.com/sourcegraph/appdash"
appdashot "sourcegraph.com/sourcegraph/appdash/opentracing" appdashot "sourcegraph.com/sourcegraph/appdash/opentracing"
@ -40,8 +40,7 @@ func main() {
thriftProtocol = flag.String("thrift.protocol", "binary", "binary, compact, json, simplejson") thriftProtocol = flag.String("thrift.protocol", "binary", "binary, compact, json, simplejson")
thriftBufferSize = flag.Int("thrift.buffer.size", 0, "0 for unbuffered") thriftBufferSize = flag.Int("thrift.buffer.size", 0, "0 for unbuffered")
thriftFramed = flag.Bool("thrift.framed", false, "true to enable framing") thriftFramed = flag.Bool("thrift.framed", false, "true to enable framing")
zipkinAddr = flag.String("zipkin.addr", "", "Enable Zipkin tracing via a Zipkin HTTP Collector endpoint") zipkinAddr = flag.String("zipkin.addr", "", "Enable Zipkin tracing via a Kafka Collector host:port")
zipkinKafkaAddr = flag.String("zipkin.kafka.addr", "", "Enable Zipkin tracing via a Kafka server host:port")
appdashAddr = flag.String("appdash.addr", "", "Enable Appdash tracing via an Appdash server host:port") appdashAddr = flag.String("appdash.addr", "", "Enable Appdash tracing via an Appdash server host:port")
lightstepToken = flag.String("lightstep.token", "", "Enable LightStep tracing via a LightStep access token") lightstepToken = flag.String("lightstep.token", "", "Enable LightStep tracing via a LightStep access token")
method = flag.String("method", "sum", "sum, concat") method = flag.String("method", "sum", "sum, concat")
@ -58,34 +57,16 @@ func main() {
var tracer stdopentracing.Tracer var tracer stdopentracing.Tracer
{ {
if *zipkinAddr != "" { if *zipkinAddr != "" {
// endpoint typically looks like: http://zipkinhost:9411/api/v1/spans
collector, err := zipkin.NewHTTPCollector(*zipkinAddr)
if err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1)
}
defer collector.Close()
tracer, err = zipkin.NewTracer(
zipkin.NewRecorder(collector, false, "0.0.0.0:0", "addcli"),
)
if err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1)
}
} else if *zipkinKafkaAddr != "" {
collector, err := zipkin.NewKafkaCollector( collector, err := zipkin.NewKafkaCollector(
strings.Split(*zipkinKafkaAddr, ","), strings.Split(*zipkinAddr, ","),
zipkin.KafkaLogger(log.NewNopLogger()), zipkin.KafkaLogger(log.NewNopLogger()),
) )
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err) fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1) os.Exit(1)
} }
defer collector.Close()
tracer, err = zipkin.NewTracer( tracer, err = zipkin.NewTracer(
zipkin.NewRecorder(collector, false, "0.0.0.0:0", "addcli"), zipkin.NewRecorder(collector, false, "localhost:8000", "addcli"),
) )
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err) fmt.Fprintf(os.Stderr, "%v\n", err)

View File

@ -1,7 +1,6 @@
package main package main
import ( import (
"context"
"flag" "flag"
"fmt" "fmt"
"net" "net"
@ -17,6 +16,7 @@ import (
stdopentracing "github.com/opentracing/opentracing-go" stdopentracing "github.com/opentracing/opentracing-go"
zipkin "github.com/openzipkin/zipkin-go-opentracing" zipkin "github.com/openzipkin/zipkin-go-opentracing"
stdprometheus "github.com/prometheus/client_golang/prometheus" stdprometheus "github.com/prometheus/client_golang/prometheus"
"golang.org/x/net/context"
"google.golang.org/grpc" "google.golang.org/grpc"
"sourcegraph.com/sourcegraph/appdash" "sourcegraph.com/sourcegraph/appdash"
appdashot "sourcegraph.com/sourcegraph/appdash/opentracing" appdashot "sourcegraph.com/sourcegraph/appdash/opentracing"
@ -40,8 +40,7 @@ func main() {
thriftProtocol = flag.String("thrift.protocol", "binary", "binary, compact, json, simplejson") thriftProtocol = flag.String("thrift.protocol", "binary", "binary, compact, json, simplejson")
thriftBufferSize = flag.Int("thrift.buffer.size", 0, "0 for unbuffered") thriftBufferSize = flag.Int("thrift.buffer.size", 0, "0 for unbuffered")
thriftFramed = flag.Bool("thrift.framed", false, "true to enable framing") thriftFramed = flag.Bool("thrift.framed", false, "true to enable framing")
zipkinAddr = flag.String("zipkin.addr", "", "Enable Zipkin tracing via a Zipkin HTTP Collector endpoint") zipkinAddr = flag.String("zipkin.addr", "", "Enable Zipkin tracing via a Kafka server host:port")
zipkinKafkaAddr = flag.String("zipkin.kafka.addr", "", "Enable Zipkin tracing via a Kafka server host:port")
appdashAddr = flag.String("appdash.addr", "", "Enable Appdash tracing via an Appdash server host:port") appdashAddr = flag.String("appdash.addr", "", "Enable Appdash tracing via an Appdash server host:port")
lightstepToken = flag.String("lightstep.token", "", "Enable LightStep tracing via a LightStep access token") lightstepToken = flag.String("lightstep.token", "", "Enable LightStep tracing via a LightStep access token")
) )
@ -51,8 +50,8 @@ func main() {
var logger log.Logger var logger log.Logger
{ {
logger = log.NewLogfmtLogger(os.Stdout) logger = log.NewLogfmtLogger(os.Stdout)
logger = log.With(logger, "ts", log.DefaultTimestampUTC) logger = log.NewContext(logger).With("ts", log.DefaultTimestampUTC)
logger = log.With(logger, "caller", log.DefaultCaller) logger = log.NewContext(logger).With("caller", log.DefaultCaller)
} }
logger.Log("msg", "hello") logger.Log("msg", "hello")
defer logger.Log("msg", "goodbye") defer logger.Log("msg", "goodbye")
@ -86,38 +85,16 @@ func main() {
var tracer stdopentracing.Tracer var tracer stdopentracing.Tracer
{ {
if *zipkinAddr != "" { if *zipkinAddr != "" {
logger := log.With(logger, "tracer", "ZipkinHTTP") logger := log.NewContext(logger).With("tracer", "Zipkin")
logger.Log("addr", *zipkinAddr) logger.Log("addr", *zipkinAddr)
// endpoint typically looks like: http://zipkinhost:9411/api/v1/spans
collector, err := zipkin.NewHTTPCollector(*zipkinAddr)
if err != nil {
logger.Log("err", err)
os.Exit(1)
}
defer collector.Close()
tracer, err = zipkin.NewTracer(
zipkin.NewRecorder(collector, false, "localhost:80", "addsvc"),
)
if err != nil {
logger.Log("err", err)
os.Exit(1)
}
} else if *zipkinKafkaAddr != "" {
logger := log.With(logger, "tracer", "ZipkinKafka")
logger.Log("addr", *zipkinKafkaAddr)
collector, err := zipkin.NewKafkaCollector( collector, err := zipkin.NewKafkaCollector(
strings.Split(*zipkinKafkaAddr, ","), strings.Split(*zipkinAddr, ","),
zipkin.KafkaLogger(log.NewNopLogger()), zipkin.KafkaLogger(logger),
) )
if err != nil { if err != nil {
logger.Log("err", err) logger.Log("err", err)
os.Exit(1) os.Exit(1)
} }
defer collector.Close()
tracer, err = zipkin.NewTracer( tracer, err = zipkin.NewTracer(
zipkin.NewRecorder(collector, false, "localhost:80", "addsvc"), zipkin.NewRecorder(collector, false, "localhost:80", "addsvc"),
) )
@ -126,18 +103,18 @@ func main() {
os.Exit(1) os.Exit(1)
} }
} else if *appdashAddr != "" { } else if *appdashAddr != "" {
logger := log.With(logger, "tracer", "Appdash") logger := log.NewContext(logger).With("tracer", "Appdash")
logger.Log("addr", *appdashAddr) logger.Log("addr", *appdashAddr)
tracer = appdashot.NewTracer(appdash.NewRemoteCollector(*appdashAddr)) tracer = appdashot.NewTracer(appdash.NewRemoteCollector(*appdashAddr))
} else if *lightstepToken != "" { } else if *lightstepToken != "" {
logger := log.With(logger, "tracer", "LightStep") logger := log.NewContext(logger).With("tracer", "LightStep")
logger.Log() // probably don't want to print out the token :) logger.Log() // probably don't want to print out the token :)
tracer = lightstep.NewTracer(lightstep.Options{ tracer = lightstep.NewTracer(lightstep.Options{
AccessToken: *lightstepToken, AccessToken: *lightstepToken,
}) })
defer lightstep.FlushLightStepTracer(tracer) defer lightstep.FlushLightStepTracer(tracer)
} else { } else {
logger := log.With(logger, "tracer", "none") logger := log.NewContext(logger).With("tracer", "none")
logger.Log() logger.Log()
tracer = stdopentracing.GlobalTracer() // no-op tracer = stdopentracing.GlobalTracer() // no-op
} }
@ -155,7 +132,7 @@ func main() {
var sumEndpoint endpoint.Endpoint var sumEndpoint endpoint.Endpoint
{ {
sumDuration := duration.With("method", "Sum") sumDuration := duration.With("method", "Sum")
sumLogger := log.With(logger, "method", "Sum") sumLogger := log.NewContext(logger).With("method", "Sum")
sumEndpoint = addsvc.MakeSumEndpoint(service) sumEndpoint = addsvc.MakeSumEndpoint(service)
sumEndpoint = opentracing.TraceServer(tracer, "Sum")(sumEndpoint) sumEndpoint = opentracing.TraceServer(tracer, "Sum")(sumEndpoint)
@ -165,7 +142,7 @@ func main() {
var concatEndpoint endpoint.Endpoint var concatEndpoint endpoint.Endpoint
{ {
concatDuration := duration.With("method", "Concat") concatDuration := duration.With("method", "Concat")
concatLogger := log.With(logger, "method", "Concat") concatLogger := log.NewContext(logger).With("method", "Concat")
concatEndpoint = addsvc.MakeConcatEndpoint(service) concatEndpoint = addsvc.MakeConcatEndpoint(service)
concatEndpoint = opentracing.TraceServer(tracer, "Concat")(concatEndpoint) concatEndpoint = opentracing.TraceServer(tracer, "Concat")(concatEndpoint)
@ -190,7 +167,7 @@ func main() {
// Debug listener. // Debug listener.
go func() { go func() {
logger := log.With(logger, "transport", "debug") logger := log.NewContext(logger).With("transport", "debug")
m := http.NewServeMux() m := http.NewServeMux()
m.Handle("/debug/pprof/", http.HandlerFunc(pprof.Index)) m.Handle("/debug/pprof/", http.HandlerFunc(pprof.Index))
@ -206,15 +183,15 @@ func main() {
// HTTP transport. // HTTP transport.
go func() { go func() {
logger := log.With(logger, "transport", "HTTP") logger := log.NewContext(logger).With("transport", "HTTP")
h := addsvc.MakeHTTPHandler(endpoints, tracer, logger) h := addsvc.MakeHTTPHandler(ctx, endpoints, tracer, logger)
logger.Log("addr", *httpAddr) logger.Log("addr", *httpAddr)
errc <- http.ListenAndServe(*httpAddr, h) errc <- http.ListenAndServe(*httpAddr, h)
}() }()
// gRPC transport. // gRPC transport.
go func() { go func() {
logger := log.With(logger, "transport", "gRPC") logger := log.NewContext(logger).With("transport", "gRPC")
ln, err := net.Listen("tcp", *grpcAddr) ln, err := net.Listen("tcp", *grpcAddr)
if err != nil { if err != nil {
@ -222,7 +199,7 @@ func main() {
return return
} }
srv := addsvc.MakeGRPCServer(endpoints, tracer, logger) srv := addsvc.MakeGRPCServer(ctx, endpoints, tracer, logger)
s := grpc.NewServer() s := grpc.NewServer()
pb.RegisterAddServer(s, srv) pb.RegisterAddServer(s, srv)
@ -232,7 +209,7 @@ func main() {
// Thrift transport. // Thrift transport.
go func() { go func() {
logger := log.With(logger, "transport", "Thrift") logger := log.NewContext(logger).With("transport", "Thrift")
var protocolFactory thrift.TProtocolFactory var protocolFactory thrift.TProtocolFactory
switch *thriftProtocol { switch *thriftProtocol {

View File

@ -6,10 +6,11 @@ package addsvc
// formats. It also includes endpoint middlewares. // formats. It also includes endpoint middlewares.
import ( import (
"context"
"fmt" "fmt"
"time" "time"
"golang.org/x/net/context"
"github.com/go-kit/kit/endpoint" "github.com/go-kit/kit/endpoint"
"github.com/go-kit/kit/log" "github.com/go-kit/kit/log"
"github.com/go-kit/kit/metrics" "github.com/go-kit/kit/metrics"

View File

@ -4,10 +4,11 @@ package addsvc
// implementation. It also includes service middlewares. // implementation. It also includes service middlewares.
import ( import (
"context"
"errors" "errors"
"time" "time"
"golang.org/x/net/context"
"github.com/go-kit/kit/log" "github.com/go-kit/kit/log"
"github.com/go-kit/kit/metrics" "github.com/go-kit/kit/metrics"
) )

View File

@ -4,10 +4,8 @@ package addsvc
// It utilizes the transport/grpc.Server. // It utilizes the transport/grpc.Server.
import ( import (
"context"
stdopentracing "github.com/opentracing/opentracing-go" stdopentracing "github.com/opentracing/opentracing-go"
oldcontext "golang.org/x/net/context" "golang.org/x/net/context"
"github.com/go-kit/kit/examples/addsvc/pb" "github.com/go-kit/kit/examples/addsvc/pb"
"github.com/go-kit/kit/log" "github.com/go-kit/kit/log"
@ -16,18 +14,20 @@ import (
) )
// MakeGRPCServer makes a set of endpoints available as a gRPC AddServer. // MakeGRPCServer makes a set of endpoints available as a gRPC AddServer.
func MakeGRPCServer(endpoints Endpoints, tracer stdopentracing.Tracer, logger log.Logger) pb.AddServer { func MakeGRPCServer(ctx context.Context, endpoints Endpoints, tracer stdopentracing.Tracer, logger log.Logger) pb.AddServer {
options := []grpctransport.ServerOption{ options := []grpctransport.ServerOption{
grpctransport.ServerErrorLogger(logger), grpctransport.ServerErrorLogger(logger),
} }
return &grpcServer{ return &grpcServer{
sum: grpctransport.NewServer( sum: grpctransport.NewServer(
ctx,
endpoints.SumEndpoint, endpoints.SumEndpoint,
DecodeGRPCSumRequest, DecodeGRPCSumRequest,
EncodeGRPCSumResponse, EncodeGRPCSumResponse,
append(options, grpctransport.ServerBefore(opentracing.FromGRPCRequest(tracer, "Sum", logger)))..., append(options, grpctransport.ServerBefore(opentracing.FromGRPCRequest(tracer, "Sum", logger)))...,
), ),
concat: grpctransport.NewServer( concat: grpctransport.NewServer(
ctx,
endpoints.ConcatEndpoint, endpoints.ConcatEndpoint,
DecodeGRPCConcatRequest, DecodeGRPCConcatRequest,
EncodeGRPCConcatResponse, EncodeGRPCConcatResponse,
@ -41,7 +41,7 @@ type grpcServer struct {
concat grpctransport.Handler concat grpctransport.Handler
} }
func (s *grpcServer) Sum(ctx oldcontext.Context, req *pb.SumRequest) (*pb.SumReply, error) { func (s *grpcServer) Sum(ctx context.Context, req *pb.SumRequest) (*pb.SumReply, error) {
_, rep, err := s.sum.ServeGRPC(ctx, req) _, rep, err := s.sum.ServeGRPC(ctx, req)
if err != nil { if err != nil {
return nil, err return nil, err
@ -49,7 +49,7 @@ func (s *grpcServer) Sum(ctx oldcontext.Context, req *pb.SumRequest) (*pb.SumRep
return rep.(*pb.SumReply), nil return rep.(*pb.SumReply), nil
} }
func (s *grpcServer) Concat(ctx oldcontext.Context, req *pb.ConcatRequest) (*pb.ConcatReply, error) { func (s *grpcServer) Concat(ctx context.Context, req *pb.ConcatRequest) (*pb.ConcatReply, error) {
_, rep, err := s.concat.ServeGRPC(ctx, req) _, rep, err := s.concat.ServeGRPC(ctx, req)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -5,13 +5,13 @@ package addsvc
import ( import (
"bytes" "bytes"
"context"
"encoding/json" "encoding/json"
"errors" "errors"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
stdopentracing "github.com/opentracing/opentracing-go" stdopentracing "github.com/opentracing/opentracing-go"
"golang.org/x/net/context"
"github.com/go-kit/kit/log" "github.com/go-kit/kit/log"
"github.com/go-kit/kit/tracing/opentracing" "github.com/go-kit/kit/tracing/opentracing"
@ -20,19 +20,21 @@ import (
// MakeHTTPHandler returns a handler that makes a set of endpoints available // MakeHTTPHandler returns a handler that makes a set of endpoints available
// on predefined paths. // on predefined paths.
func MakeHTTPHandler(endpoints Endpoints, tracer stdopentracing.Tracer, logger log.Logger) http.Handler { func MakeHTTPHandler(ctx context.Context, endpoints Endpoints, tracer stdopentracing.Tracer, logger log.Logger) http.Handler {
options := []httptransport.ServerOption{ options := []httptransport.ServerOption{
httptransport.ServerErrorEncoder(errorEncoder), httptransport.ServerErrorEncoder(errorEncoder),
httptransport.ServerErrorLogger(logger), httptransport.ServerErrorLogger(logger),
} }
m := http.NewServeMux() m := http.NewServeMux()
m.Handle("/sum", httptransport.NewServer( m.Handle("/sum", httptransport.NewServer(
ctx,
endpoints.SumEndpoint, endpoints.SumEndpoint,
DecodeHTTPSumRequest, DecodeHTTPSumRequest,
EncodeHTTPGenericResponse, EncodeHTTPGenericResponse,
append(options, httptransport.ServerBefore(opentracing.FromHTTPRequest(tracer, "Sum", logger)))..., append(options, httptransport.ServerBefore(opentracing.FromHTTPRequest(tracer, "Sum", logger)))...,
)) ))
m.Handle("/concat", httptransport.NewServer( m.Handle("/concat", httptransport.NewServer(
ctx,
endpoints.ConcatEndpoint, endpoints.ConcatEndpoint,
DecodeHTTPConcatRequest, DecodeHTTPConcatRequest,
EncodeHTTPGenericResponse, EncodeHTTPGenericResponse,
@ -45,9 +47,18 @@ func errorEncoder(_ context.Context, err error, w http.ResponseWriter) {
code := http.StatusInternalServerError code := http.StatusInternalServerError
msg := err.Error() msg := err.Error()
switch err { if e, ok := err.(httptransport.Error); ok {
case ErrTwoZeroes, ErrMaxSizeExceeded, ErrIntOverflow: msg = e.Err.Error()
code = http.StatusBadRequest switch e.Domain {
case httptransport.DomainDecode:
code = http.StatusBadRequest
case httptransport.DomainDo:
switch e.Err {
case ErrTwoZeroes, ErrMaxSizeExceeded, ErrIntOverflow:
code = http.StatusBadRequest
}
}
} }
w.WriteHeader(code) w.WriteHeader(code)

View File

@ -7,7 +7,7 @@ package addsvc
// yet. See https://github.com/go-kit/kit/issues/184. // yet. See https://github.com/go-kit/kit/issues/184.
import ( import (
"context" "golang.org/x/net/context"
"github.com/go-kit/kit/endpoint" "github.com/go-kit/kit/endpoint"
thriftadd "github.com/go-kit/kit/examples/addsvc/thrift/gen-go/addsvc" thriftadd "github.com/go-kit/kit/examples/addsvc/thrift/gen-go/addsvc"

View File

@ -2,7 +2,6 @@ package main
import ( import (
"bytes" "bytes"
"context"
"encoding/json" "encoding/json"
"flag" "flag"
"fmt" "fmt"
@ -19,6 +18,7 @@ import (
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/hashicorp/consul/api" "github.com/hashicorp/consul/api"
stdopentracing "github.com/opentracing/opentracing-go" stdopentracing "github.com/opentracing/opentracing-go"
"golang.org/x/net/context"
"github.com/go-kit/kit/endpoint" "github.com/go-kit/kit/endpoint"
"github.com/go-kit/kit/examples/addsvc" "github.com/go-kit/kit/examples/addsvc"
@ -44,8 +44,8 @@ func main() {
var logger log.Logger var logger log.Logger
{ {
logger = log.NewLogfmtLogger(os.Stderr) logger = log.NewLogfmtLogger(os.Stderr)
logger = log.With(logger, "ts", log.DefaultTimestampUTC) logger = log.NewContext(logger).With("ts", log.DefaultTimestampUTC)
logger = log.With(logger, "caller", log.DefaultCaller) logger = log.NewContext(logger).With("caller", log.DefaultCaller)
} }
// Service discovery domain. In this example we use Consul. // Service discovery domain. In this example we use Consul.
@ -104,7 +104,7 @@ func main() {
// HTTP handler, and just install it under a particular path prefix in // HTTP handler, and just install it under a particular path prefix in
// our router. // our router.
r.PathPrefix("/addsvc").Handler(http.StripPrefix("/addsvc", addsvc.MakeHTTPHandler(endpoints, tracer, logger))) r.PathPrefix("addsvc/").Handler(addsvc.MakeHTTPHandler(ctx, endpoints, tracer, logger))
} }
// stringsvc routes. // stringsvc routes.
@ -140,8 +140,8 @@ func main() {
// have to do provide it with the encode and decode functions for our // have to do provide it with the encode and decode functions for our
// stringsvc methods. // stringsvc methods.
r.Handle("/stringsvc/uppercase", httptransport.NewServer(uppercase, decodeUppercaseRequest, encodeJSONResponse)) r.Handle("/stringsvc/uppercase", httptransport.NewServer(ctx, uppercase, decodeUppercaseRequest, encodeJSONResponse))
r.Handle("/stringsvc/count", httptransport.NewServer(count, decodeCountRequest, encodeJSONResponse)) r.Handle("/stringsvc/count", httptransport.NewServer(ctx, count, decodeCountRequest, encodeJSONResponse))
} }
// Interrupt handler. // Interrupt handler.

View File

@ -1,4 +1,4 @@
package main package main
import ( import (
"flag" "flag"
@ -8,6 +8,8 @@ import (
"os/signal" "os/signal"
"syscall" "syscall"
"golang.org/x/net/context"
"github.com/go-kit/kit/examples/profilesvc" "github.com/go-kit/kit/examples/profilesvc"
"github.com/go-kit/kit/log" "github.com/go-kit/kit/log"
) )
@ -21,8 +23,13 @@ func main() {
var logger log.Logger var logger log.Logger
{ {
logger = log.NewLogfmtLogger(os.Stderr) logger = log.NewLogfmtLogger(os.Stderr)
logger = log.With(logger, "ts", log.DefaultTimestampUTC) logger = log.NewContext(logger).With("ts", log.DefaultTimestampUTC)
logger = log.With(logger, "caller", log.DefaultCaller) logger = log.NewContext(logger).With("caller", log.DefaultCaller)
}
var ctx context.Context
{
ctx = context.Background()
} }
var s profilesvc.Service var s profilesvc.Service
@ -33,7 +40,7 @@ func main() {
var h http.Handler var h http.Handler
{ {
h = profilesvc.MakeHTTPHandler(s, log.With(logger, "component", "HTTP")) h = profilesvc.MakeHTTPHandler(ctx, s, log.NewContext(logger).With("component", "HTTP"))
} }
errs := make(chan error) errs := make(chan error)

View File

@ -1,10 +1,11 @@
package profilesvc package profilesvc
import ( import (
"context"
"net/url" "net/url"
"strings" "strings"
"golang.org/x/net/context"
"github.com/go-kit/kit/endpoint" "github.com/go-kit/kit/endpoint"
httptransport "github.com/go-kit/kit/transport/http" httptransport "github.com/go-kit/kit/transport/http"
) )

View File

@ -1,9 +1,10 @@
package profilesvc package profilesvc
import ( import (
"context"
"time" "time"
"golang.org/x/net/context"
"github.com/go-kit/kit/log" "github.com/go-kit/kit/log"
) )

View File

@ -1,9 +1,10 @@
package profilesvc package profilesvc
import ( import (
"context"
"errors" "errors"
"sync" "sync"
"golang.org/x/net/context"
) )
// Service is a simple CRUD interface for user profiles. // Service is a simple CRUD interface for user profiles.

View File

@ -4,14 +4,15 @@ package profilesvc
import ( import (
"bytes" "bytes"
"context"
"encoding/json" "encoding/json"
"errors" "errors"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"net/url" "net/url"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"golang.org/x/net/context"
"github.com/go-kit/kit/log" "github.com/go-kit/kit/log"
httptransport "github.com/go-kit/kit/transport/http" httptransport "github.com/go-kit/kit/transport/http"
@ -25,7 +26,7 @@ var (
// MakeHTTPHandler mounts all of the service endpoints into an http.Handler. // MakeHTTPHandler mounts all of the service endpoints into an http.Handler.
// Useful in a profilesvc server. // Useful in a profilesvc server.
func MakeHTTPHandler(s Service, logger log.Logger) http.Handler { func MakeHTTPHandler(ctx context.Context, s Service, logger log.Logger) http.Handler {
r := mux.NewRouter() r := mux.NewRouter()
e := MakeServerEndpoints(s) e := MakeServerEndpoints(s)
options := []httptransport.ServerOption{ options := []httptransport.ServerOption{
@ -44,54 +45,63 @@ func MakeHTTPHandler(s Service, logger log.Logger) http.Handler {
// DELETE /profiles/:id/addresses/:addressID remove an address // DELETE /profiles/:id/addresses/:addressID remove an address
r.Methods("POST").Path("/profiles/").Handler(httptransport.NewServer( r.Methods("POST").Path("/profiles/").Handler(httptransport.NewServer(
ctx,
e.PostProfileEndpoint, e.PostProfileEndpoint,
decodePostProfileRequest, decodePostProfileRequest,
encodeResponse, encodeResponse,
options..., options...,
)) ))
r.Methods("GET").Path("/profiles/{id}").Handler(httptransport.NewServer( r.Methods("GET").Path("/profiles/{id}").Handler(httptransport.NewServer(
ctx,
e.GetProfileEndpoint, e.GetProfileEndpoint,
decodeGetProfileRequest, decodeGetProfileRequest,
encodeResponse, encodeResponse,
options..., options...,
)) ))
r.Methods("PUT").Path("/profiles/{id}").Handler(httptransport.NewServer( r.Methods("PUT").Path("/profiles/{id}").Handler(httptransport.NewServer(
ctx,
e.PutProfileEndpoint, e.PutProfileEndpoint,
decodePutProfileRequest, decodePutProfileRequest,
encodeResponse, encodeResponse,
options..., options...,
)) ))
r.Methods("PATCH").Path("/profiles/{id}").Handler(httptransport.NewServer( r.Methods("PATCH").Path("/profiles/{id}").Handler(httptransport.NewServer(
ctx,
e.PatchProfileEndpoint, e.PatchProfileEndpoint,
decodePatchProfileRequest, decodePatchProfileRequest,
encodeResponse, encodeResponse,
options..., options...,
)) ))
r.Methods("DELETE").Path("/profiles/{id}").Handler(httptransport.NewServer( r.Methods("DELETE").Path("/profiles/{id}").Handler(httptransport.NewServer(
ctx,
e.DeleteProfileEndpoint, e.DeleteProfileEndpoint,
decodeDeleteProfileRequest, decodeDeleteProfileRequest,
encodeResponse, encodeResponse,
options..., options...,
)) ))
r.Methods("GET").Path("/profiles/{id}/addresses/").Handler(httptransport.NewServer( r.Methods("GET").Path("/profiles/{id}/addresses/").Handler(httptransport.NewServer(
ctx,
e.GetAddressesEndpoint, e.GetAddressesEndpoint,
decodeGetAddressesRequest, decodeGetAddressesRequest,
encodeResponse, encodeResponse,
options..., options...,
)) ))
r.Methods("GET").Path("/profiles/{id}/addresses/{addressID}").Handler(httptransport.NewServer( r.Methods("GET").Path("/profiles/{id}/addresses/{addressID}").Handler(httptransport.NewServer(
ctx,
e.GetAddressEndpoint, e.GetAddressEndpoint,
decodeGetAddressRequest, decodeGetAddressRequest,
encodeResponse, encodeResponse,
options..., options...,
)) ))
r.Methods("POST").Path("/profiles/{id}/addresses/").Handler(httptransport.NewServer( r.Methods("POST").Path("/profiles/{id}/addresses/").Handler(httptransport.NewServer(
ctx,
e.PostAddressEndpoint, e.PostAddressEndpoint,
decodePostAddressRequest, decodePostAddressRequest,
encodeResponse, encodeResponse,
options..., options...,
)) ))
r.Methods("DELETE").Path("/profiles/{id}/addresses/{addressID}").Handler(httptransport.NewServer( r.Methods("DELETE").Path("/profiles/{id}/addresses/{addressID}").Handler(httptransport.NewServer(
ctx,
e.DeleteAddressEndpoint, e.DeleteAddressEndpoint,
decodeDeleteAddressRequest, decodeDeleteAddressRequest,
encodeResponse, encodeResponse,
@ -395,6 +405,16 @@ func codeFrom(err error) int {
case ErrAlreadyExists, ErrInconsistentIDs: case ErrAlreadyExists, ErrInconsistentIDs:
return http.StatusBadRequest return http.StatusBadRequest
default: default:
if e, ok := err.(httptransport.Error); ok {
switch e.Domain {
case httptransport.DomainDecode:
return http.StatusBadRequest
case httptransport.DomainDo:
return http.StatusServiceUnavailable
default:
return http.StatusInternalServerError
}
}
return http.StatusInternalServerError return http.StatusInternalServerError
} }
} }

View File

@ -1,9 +1,10 @@
package booking package booking
import ( import (
"context"
"time" "time"
"golang.org/x/net/context"
"github.com/go-kit/kit/endpoint" "github.com/go-kit/kit/endpoint"
"github.com/go-kit/kit/examples/shipping/cargo" "github.com/go-kit/kit/examples/shipping/cargo"
@ -42,10 +43,10 @@ type loadCargoResponse struct {
func (r loadCargoResponse) error() error { return r.Err } func (r loadCargoResponse) error() error { return r.Err }
func makeLoadCargoEndpoint(s Service) endpoint.Endpoint { func makeLoadCargoEndpoint(bs Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) { return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(loadCargoRequest) req := request.(loadCargoRequest)
c, err := s.LoadCargo(req.ID) c, err := bs.LoadCargo(req.ID)
return loadCargoResponse{Cargo: &c, Err: err}, nil return loadCargoResponse{Cargo: &c, Err: err}, nil
} }
} }

View File

@ -1,13 +1,13 @@
package booking package booking
import ( import (
"context"
"encoding/json" "encoding/json"
"errors" "errors"
"net/http" "net/http"
"time" "time"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"golang.org/x/net/context"
kitlog "github.com/go-kit/kit/log" kitlog "github.com/go-kit/kit/log"
kithttp "github.com/go-kit/kit/transport/http" kithttp "github.com/go-kit/kit/transport/http"
@ -17,49 +17,56 @@ import (
) )
// MakeHandler returns a handler for the booking service. // MakeHandler returns a handler for the booking service.
func MakeHandler(bs Service, logger kitlog.Logger) http.Handler { func MakeHandler(ctx context.Context, bs Service, logger kitlog.Logger) http.Handler {
opts := []kithttp.ServerOption{ opts := []kithttp.ServerOption{
kithttp.ServerErrorLogger(logger), kithttp.ServerErrorLogger(logger),
kithttp.ServerErrorEncoder(encodeError), kithttp.ServerErrorEncoder(encodeError),
} }
bookCargoHandler := kithttp.NewServer( bookCargoHandler := kithttp.NewServer(
ctx,
makeBookCargoEndpoint(bs), makeBookCargoEndpoint(bs),
decodeBookCargoRequest, decodeBookCargoRequest,
encodeResponse, encodeResponse,
opts..., opts...,
) )
loadCargoHandler := kithttp.NewServer( loadCargoHandler := kithttp.NewServer(
ctx,
makeLoadCargoEndpoint(bs), makeLoadCargoEndpoint(bs),
decodeLoadCargoRequest, decodeLoadCargoRequest,
encodeResponse, encodeResponse,
opts..., opts...,
) )
requestRoutesHandler := kithttp.NewServer( requestRoutesHandler := kithttp.NewServer(
ctx,
makeRequestRoutesEndpoint(bs), makeRequestRoutesEndpoint(bs),
decodeRequestRoutesRequest, decodeRequestRoutesRequest,
encodeResponse, encodeResponse,
opts..., opts...,
) )
assignToRouteHandler := kithttp.NewServer( assignToRouteHandler := kithttp.NewServer(
ctx,
makeAssignToRouteEndpoint(bs), makeAssignToRouteEndpoint(bs),
decodeAssignToRouteRequest, decodeAssignToRouteRequest,
encodeResponse, encodeResponse,
opts..., opts...,
) )
changeDestinationHandler := kithttp.NewServer( changeDestinationHandler := kithttp.NewServer(
ctx,
makeChangeDestinationEndpoint(bs), makeChangeDestinationEndpoint(bs),
decodeChangeDestinationRequest, decodeChangeDestinationRequest,
encodeResponse, encodeResponse,
opts..., opts...,
) )
listCargosHandler := kithttp.NewServer( listCargosHandler := kithttp.NewServer(
ctx,
makeListCargosEndpoint(bs), makeListCargosEndpoint(bs),
decodeListCargosRequest, decodeListCargosRequest,
encodeResponse, encodeResponse,
opts..., opts...,
) )
listLocationsHandler := kithttp.NewServer( listLocationsHandler := kithttp.NewServer(
ctx,
makeListLocationsEndpoint(bs), makeListLocationsEndpoint(bs),
decodeListLocationsRequest, decodeListLocationsRequest,
encodeResponse, encodeResponse,

View File

@ -1,9 +1,10 @@
package handling package handling
import ( import (
"context"
"time" "time"
"golang.org/x/net/context"
"github.com/go-kit/kit/endpoint" "github.com/go-kit/kit/endpoint"
"github.com/go-kit/kit/examples/shipping/cargo" "github.com/go-kit/kit/examples/shipping/cargo"

View File

@ -1,12 +1,12 @@
package handling package handling
import ( import (
"context"
"encoding/json" "encoding/json"
"net/http" "net/http"
"time" "time"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"golang.org/x/net/context"
kitlog "github.com/go-kit/kit/log" kitlog "github.com/go-kit/kit/log"
kithttp "github.com/go-kit/kit/transport/http" kithttp "github.com/go-kit/kit/transport/http"
@ -17,7 +17,7 @@ import (
) )
// MakeHandler returns a handler for the handling service. // MakeHandler returns a handler for the handling service.
func MakeHandler(hs Service, logger kitlog.Logger) http.Handler { func MakeHandler(ctx context.Context, hs Service, logger kitlog.Logger) http.Handler {
r := mux.NewRouter() r := mux.NewRouter()
opts := []kithttp.ServerOption{ opts := []kithttp.ServerOption{
@ -26,6 +26,7 @@ func MakeHandler(hs Service, logger kitlog.Logger) http.Handler {
} }
registerIncidentHandler := kithttp.NewServer( registerIncidentHandler := kithttp.NewServer(
ctx,
makeRegisterIncidentEndpoint(hs), makeRegisterIncidentEndpoint(hs),
decodeRegisterIncidentRequest, decodeRegisterIncidentRequest,
encodeResponse, encodeResponse,

View File

@ -1,9 +1,7 @@
// Package inspection provides means to inspect cargos. // Package inspection provides means to inspect cargos.
package inspection package inspection
import ( import "github.com/go-kit/kit/examples/shipping/cargo"
"github.com/go-kit/kit/examples/shipping/cargo"
)
// EventHandler provides means of subscribing to inspection events. // EventHandler provides means of subscribing to inspection events.
type EventHandler interface { type EventHandler interface {

View File

@ -1,9 +1,7 @@
// Package location provides the Location aggregate. // Package location provides the Location aggregate.
package location package location
import ( import "errors"
"errors"
)
// UNLocode is the United Nations location code that uniquely identifies a // UNLocode is the United Nations location code that uniquely identifies a
// particular location. // particular location.

View File

@ -1,7 +1,6 @@
package main package main
import ( import (
"context"
"flag" "flag"
"fmt" "fmt"
"net/http" "net/http"
@ -12,6 +11,7 @@ import (
"time" "time"
stdprometheus "github.com/prometheus/client_golang/prometheus" stdprometheus "github.com/prometheus/client_golang/prometheus"
"golang.org/x/net/context"
"github.com/go-kit/kit/log" "github.com/go-kit/kit/log"
kitprometheus "github.com/go-kit/kit/metrics/prometheus" kitprometheus "github.com/go-kit/kit/metrics/prometheus"
@ -47,7 +47,7 @@ func main() {
var logger log.Logger var logger log.Logger
logger = log.NewLogfmtLogger(os.Stderr) logger = log.NewLogfmtLogger(os.Stderr)
logger = &serializedLogger{Logger: logger} logger = &serializedLogger{Logger: logger}
logger = log.With(logger, "ts", log.DefaultTimestampUTC) logger = log.NewContext(logger).With("ts", log.DefaultTimestampUTC)
var ( var (
cargos = inmem.NewCargoRepository() cargos = inmem.NewCargoRepository()
@ -78,7 +78,7 @@ func main() {
var bs booking.Service var bs booking.Service
bs = booking.NewService(cargos, locations, handlingEvents, rs) bs = booking.NewService(cargos, locations, handlingEvents, rs)
bs = booking.NewLoggingService(log.With(logger, "component", "booking"), bs) bs = booking.NewLoggingService(log.NewContext(logger).With("component", "booking"), bs)
bs = booking.NewInstrumentingService( bs = booking.NewInstrumentingService(
kitprometheus.NewCounterFrom(stdprometheus.CounterOpts{ kitprometheus.NewCounterFrom(stdprometheus.CounterOpts{
Namespace: "api", Namespace: "api",
@ -97,7 +97,7 @@ func main() {
var ts tracking.Service var ts tracking.Service
ts = tracking.NewService(cargos, handlingEvents) ts = tracking.NewService(cargos, handlingEvents)
ts = tracking.NewLoggingService(log.With(logger, "component", "tracking"), ts) ts = tracking.NewLoggingService(log.NewContext(logger).With("component", "tracking"), ts)
ts = tracking.NewInstrumentingService( ts = tracking.NewInstrumentingService(
kitprometheus.NewCounterFrom(stdprometheus.CounterOpts{ kitprometheus.NewCounterFrom(stdprometheus.CounterOpts{
Namespace: "api", Namespace: "api",
@ -116,7 +116,7 @@ func main() {
var hs handling.Service var hs handling.Service
hs = handling.NewService(handlingEvents, handlingEventFactory, handlingEventHandler) hs = handling.NewService(handlingEvents, handlingEventFactory, handlingEventHandler)
hs = handling.NewLoggingService(log.With(logger, "component", "handling"), hs) hs = handling.NewLoggingService(log.NewContext(logger).With("component", "handling"), hs)
hs = handling.NewInstrumentingService( hs = handling.NewInstrumentingService(
kitprometheus.NewCounterFrom(stdprometheus.CounterOpts{ kitprometheus.NewCounterFrom(stdprometheus.CounterOpts{
Namespace: "api", Namespace: "api",
@ -133,13 +133,13 @@ func main() {
hs, hs,
) )
httpLogger := log.With(logger, "component", "http") httpLogger := log.NewContext(logger).With("component", "http")
mux := http.NewServeMux() mux := http.NewServeMux()
mux.Handle("/booking/v1/", booking.MakeHandler(bs, httpLogger)) mux.Handle("/booking/v1/", booking.MakeHandler(ctx, bs, httpLogger))
mux.Handle("/tracking/v1/", tracking.MakeHandler(ts, httpLogger)) mux.Handle("/tracking/v1/", tracking.MakeHandler(ctx, ts, httpLogger))
mux.Handle("/handling/v1/", handling.MakeHandler(hs, httpLogger)) mux.Handle("/handling/v1/", handling.MakeHandler(ctx, hs, httpLogger))
http.Handle("/", accessControl(mux)) http.Handle("/", accessControl(mux))
http.Handle("/metrics", stdprometheus.Handler()) http.Handle("/metrics", stdprometheus.Handler())

View File

@ -1,12 +1,13 @@
package routing package routing
import ( import (
"context"
"encoding/json" "encoding/json"
"net/http" "net/http"
"net/url" "net/url"
"time" "time"
"golang.org/x/net/context"
"github.com/go-kit/kit/circuitbreaker" "github.com/go-kit/kit/circuitbreaker"
"github.com/go-kit/kit/endpoint" "github.com/go-kit/kit/endpoint"
kithttp "github.com/go-kit/kit/transport/http" kithttp "github.com/go-kit/kit/transport/http"

View File

@ -3,9 +3,7 @@
// bounded context. // bounded context.
package routing package routing
import ( import "github.com/go-kit/kit/examples/shipping/cargo"
"github.com/go-kit/kit/examples/shipping/cargo"
)
// Service provides access to an external routing service. // Service provides access to an external routing service.
type Service interface { type Service interface {

View File

@ -1,7 +1,7 @@
package tracking package tracking
import ( import (
"context" "golang.org/x/net/context"
"github.com/go-kit/kit/endpoint" "github.com/go-kit/kit/endpoint"
) )

View File

@ -1,12 +1,12 @@
package tracking package tracking
import ( import (
"context"
"encoding/json" "encoding/json"
"errors" "errors"
"net/http" "net/http"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"golang.org/x/net/context"
kitlog "github.com/go-kit/kit/log" kitlog "github.com/go-kit/kit/log"
kithttp "github.com/go-kit/kit/transport/http" kithttp "github.com/go-kit/kit/transport/http"
@ -15,7 +15,7 @@ import (
) )
// MakeHandler returns a handler for the tracking service. // MakeHandler returns a handler for the tracking service.
func MakeHandler(ts Service, logger kitlog.Logger) http.Handler { func MakeHandler(ctx context.Context, ts Service, logger kitlog.Logger) http.Handler {
r := mux.NewRouter() r := mux.NewRouter()
opts := []kithttp.ServerOption{ opts := []kithttp.ServerOption{
@ -24,6 +24,7 @@ func MakeHandler(ts Service, logger kitlog.Logger) http.Handler {
} }
trackCargoHandler := kithttp.NewServer( trackCargoHandler := kithttp.NewServer(
ctx,
makeTrackCargoEndpoint(ts), makeTrackCargoEndpoint(ts),
decodeTrackCargoRequest, decodeTrackCargoRequest,
encodeResponse, encodeResponse,

View File

@ -1,13 +1,14 @@
package main package main
import ( import (
"context"
"encoding/json" "encoding/json"
"errors" "errors"
"log" "log"
"net/http" "net/http"
"strings" "strings"
"golang.org/x/net/context"
"github.com/go-kit/kit/endpoint" "github.com/go-kit/kit/endpoint"
httptransport "github.com/go-kit/kit/transport/http" httptransport "github.com/go-kit/kit/transport/http"
) )
@ -32,15 +33,18 @@ func (stringService) Count(s string) int {
} }
func main() { func main() {
ctx := context.Background()
svc := stringService{} svc := stringService{}
uppercaseHandler := httptransport.NewServer( uppercaseHandler := httptransport.NewServer(
ctx,
makeUppercaseEndpoint(svc), makeUppercaseEndpoint(svc),
decodeUppercaseRequest, decodeUppercaseRequest,
encodeResponse, encodeResponse,
) )
countHandler := httptransport.NewServer( countHandler := httptransport.NewServer(
ctx,
makeCountEndpoint(svc), makeCountEndpoint(svc),
decodeCountRequest, decodeCountRequest,
encodeResponse, encodeResponse,

View File

@ -5,6 +5,7 @@ import (
"os" "os"
stdprometheus "github.com/prometheus/client_golang/prometheus" stdprometheus "github.com/prometheus/client_golang/prometheus"
"golang.org/x/net/context"
"github.com/go-kit/kit/log" "github.com/go-kit/kit/log"
kitprometheus "github.com/go-kit/kit/metrics/prometheus" kitprometheus "github.com/go-kit/kit/metrics/prometheus"
@ -12,6 +13,7 @@ import (
) )
func main() { func main() {
ctx := context.Background()
logger := log.NewLogfmtLogger(os.Stderr) logger := log.NewLogfmtLogger(os.Stderr)
fieldKeys := []string{"method", "error"} fieldKeys := []string{"method", "error"}
@ -40,12 +42,14 @@ func main() {
svc = instrumentingMiddleware{requestCount, requestLatency, countResult, svc} svc = instrumentingMiddleware{requestCount, requestLatency, countResult, svc}
uppercaseHandler := httptransport.NewServer( uppercaseHandler := httptransport.NewServer(
ctx,
makeUppercaseEndpoint(svc), makeUppercaseEndpoint(svc),
decodeUppercaseRequest, decodeUppercaseRequest,
encodeResponse, encodeResponse,
) )
countHandler := httptransport.NewServer( countHandler := httptransport.NewServer(
ctx,
makeCountEndpoint(svc), makeCountEndpoint(svc),
decodeCountRequest, decodeCountRequest,
encodeResponse, encodeResponse,

View File

@ -1,10 +1,11 @@
package main package main
import ( import (
"context"
"encoding/json" "encoding/json"
"net/http" "net/http"
"golang.org/x/net/context"
"github.com/go-kit/kit/endpoint" "github.com/go-kit/kit/endpoint"
) )

View File

@ -1,12 +1,12 @@
package main package main
import ( import (
"context"
"flag" "flag"
"net/http" "net/http"
"os" "os"
stdprometheus "github.com/prometheus/client_golang/prometheus" stdprometheus "github.com/prometheus/client_golang/prometheus"
"golang.org/x/net/context"
"github.com/go-kit/kit/log" "github.com/go-kit/kit/log"
kitprometheus "github.com/go-kit/kit/metrics/prometheus" kitprometheus "github.com/go-kit/kit/metrics/prometheus"
@ -22,7 +22,9 @@ func main() {
var logger log.Logger var logger log.Logger
logger = log.NewLogfmtLogger(os.Stderr) logger = log.NewLogfmtLogger(os.Stderr)
logger = log.With(logger, "listen", *listen, "caller", log.DefaultCaller) logger = log.NewContext(logger).With("listen", *listen).With("caller", log.DefaultCaller)
ctx := context.Background()
fieldKeys := []string{"method", "error"} fieldKeys := []string{"method", "error"}
requestCount := kitprometheus.NewCounterFrom(stdprometheus.CounterOpts{ requestCount := kitprometheus.NewCounterFrom(stdprometheus.CounterOpts{
@ -46,16 +48,18 @@ func main() {
var svc StringService var svc StringService
svc = stringService{} svc = stringService{}
svc = proxyingMiddleware(context.Background(), *proxy, logger)(svc) svc = proxyingMiddleware(*proxy, ctx, logger)(svc)
svc = loggingMiddleware(logger)(svc) svc = loggingMiddleware(logger)(svc)
svc = instrumentingMiddleware(requestCount, requestLatency, countResult)(svc) svc = instrumentingMiddleware(requestCount, requestLatency, countResult)(svc)
uppercaseHandler := httptransport.NewServer( uppercaseHandler := httptransport.NewServer(
ctx,
makeUppercaseEndpoint(svc), makeUppercaseEndpoint(svc),
decodeUppercaseRequest, decodeUppercaseRequest,
encodeResponse, encodeResponse,
) )
countHandler := httptransport.NewServer( countHandler := httptransport.NewServer(
ctx,
makeCountEndpoint(svc), makeCountEndpoint(svc),
decodeCountRequest, decodeCountRequest,
encodeResponse, encodeResponse,

View File

@ -1,7 +1,6 @@
package main package main
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"net/url" "net/url"
@ -10,6 +9,7 @@ import (
jujuratelimit "github.com/juju/ratelimit" jujuratelimit "github.com/juju/ratelimit"
"github.com/sony/gobreaker" "github.com/sony/gobreaker"
"golang.org/x/net/context"
"github.com/go-kit/kit/circuitbreaker" "github.com/go-kit/kit/circuitbreaker"
"github.com/go-kit/kit/endpoint" "github.com/go-kit/kit/endpoint"
@ -20,7 +20,7 @@ import (
httptransport "github.com/go-kit/kit/transport/http" httptransport "github.com/go-kit/kit/transport/http"
) )
func proxyingMiddleware(ctx context.Context, instances string, logger log.Logger) ServiceMiddleware { func proxyingMiddleware(instances string, ctx context.Context, logger log.Logger) ServiceMiddleware {
// If instances is empty, don't proxy. // If instances is empty, don't proxy.
if instances == "" { if instances == "" {
logger.Log("proxy_to", "none") logger.Log("proxy_to", "none")

View File

@ -2,11 +2,12 @@ package main
import ( import (
"bytes" "bytes"
"context"
"encoding/json" "encoding/json"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"golang.org/x/net/context"
"github.com/go-kit/kit/endpoint" "github.com/go-kit/kit/endpoint"
) )

View File

@ -1,22 +1,20 @@
# package log # package log
`package log` provides a minimal interface for structured logging in services. `package log` provides a minimal interface for structured logging in services.
It may be wrapped to encode conventions, enforce type-safety, provide leveled It may be wrapped to encode conventions, enforce type-safety, provide leveled logging, and so on.
logging, and so on. It can be used for both typical application log events, It can be used for both typical application log events, and log-structured data streams.
and log-structured data streams.
## Structured logging ## Structured logging
Structured logging is, basically, conceding to the reality that logs are Structured logging is, basically, conceding to the reality that logs are _data_,
_data_, and warrant some level of schematic rigor. Using a stricter, and warrant some level of schematic rigor.
key/value-oriented message format for our logs, containing contextual and Using a stricter, key/value-oriented message format for our logs,
semantic information, makes it much easier to get insight into the containing contextual and semantic information,
operational activity of the systems we build. Consequently, `package log` is makes it much easier to get insight into the operational activity of the systems we build.
of the strong belief that "[the benefits of structured logging outweigh the Consequently, `package log` is of the strong belief that
minimal effort involved](https://www.thoughtworks.com/radar/techniques/structured-logging)". "[the benefits of structured logging outweigh the minimal effort involved](https://www.thoughtworks.com/radar/techniques/structured-logging)".
Migrating from unstructured to structured logging is probably a lot easier Migrating from unstructured to structured logging is probably a lot easier than you'd expect.
than you'd expect.
```go ```go
// Unstructured // Unstructured
@ -39,17 +37,17 @@ logger.Log("question", "what is the meaning of life?", "answer", 42)
// question="what is the meaning of life?" answer=42 // question="what is the meaning of life?" answer=42
``` ```
### Contextual Loggers ### Log contexts
```go ```go
func main() { func main() {
var logger log.Logger var logger log.Logger
logger = log.NewLogfmtLogger(log.NewSyncWriter(os.Stderr)) logger = log.NewLogfmtLogger(log.NewSyncWriter(os.Stderr))
logger = log.With(logger, "instance_id", 123) logger = log.NewContext(logger).With("instance_id", 123)
logger.Log("msg", "starting") logger.Log("msg", "starting")
NewWorker(log.With(logger, "component", "worker")).Run() NewWorker(log.NewContext(logger).With("component", "worker")).Run()
NewSlacker(log.With(logger, "component", "slacker")).Run() NewSlacker(log.NewContext(logger).With("component", "slacker")).Run()
} }
// Output: // Output:
@ -79,8 +77,9 @@ func main() {
// {"msg":"I sure like pie","ts":"2016/01/01 12:34:56"} // {"msg":"I sure like pie","ts":"2016/01/01 12:34:56"}
``` ```
Or, if, for legacy reasons, you need to pipe all of your logging through the Or, if, for legacy reasons,
stdlib log package, you can redirect Go kit logger to the stdlib logger. you need to pipe all of your logging through the stdlib log package,
you can redirect Go kit logger to the stdlib logger.
```go ```go
logger := kitlog.NewLogfmtLogger(kitlog.StdlibWriter{}) logger := kitlog.NewLogfmtLogger(kitlog.StdlibWriter{})
@ -95,7 +94,7 @@ logger.Log("legacy", true, "msg", "at least it's something")
```go ```go
var logger log.Logger var logger log.Logger
logger = log.NewLogfmtLogger(log.NewSyncWriter(os.Stderr)) logger = log.NewLogfmtLogger(log.NewSyncWriter(os.Stderr))
logger = log.With(logger, "ts", log.DefaultTimestampUTC, "caller", log.DefaultCaller) logger = log.NewContext(logger).With("ts", log.DefaultTimestampUTC, "caller", log.DefaultCaller)
logger.Log("msg", "hello") logger.Log("msg", "hello")
@ -105,7 +104,7 @@ logger.Log("msg", "hello")
## Supported output formats ## Supported output formats
- [Logfmt](https://brandur.org/logfmt) ([see also](https://blog.codeship.com/logfmt-a-log-format-thats-easy-to-read-and-write)) - [Logfmt](https://brandur.org/logfmt)
- JSON - JSON
## Enhancements ## Enhancements
@ -118,25 +117,27 @@ type Logger interface {
} }
``` ```
This interface, and its supporting code like is the product of much iteration This interface, and its supporting code like [log.Context](https://godoc.org/github.com/go-kit/kit/log#Context),
and evaluation. For more details on the evolution of the Logger interface, is the product of much iteration and evaluation.
see [The Hunt for a Logger Interface](http://go-talks.appspot.com/github.com/ChrisHines/talks/structured-logging/structured-logging.slide#1), For more details on the evolution of the Logger interface,
a talk by [Chris Hines](https://github.com/ChrisHines). see [The Hunt for a Logger Interface](http://go-talks.appspot.com/github.com/ChrisHines/talks/structured-logging/structured-logging.slide#1),
a talk by [Chris Hines](https://github.com/ChrisHines).
Also, please see Also, please see
[#63](https://github.com/go-kit/kit/issues/63), [#63](https://github.com/go-kit/kit/issues/63),
[#76](https://github.com/go-kit/kit/pull/76), [#76](https://github.com/go-kit/kit/pull/76),
[#131](https://github.com/go-kit/kit/issues/131), [#131](https://github.com/go-kit/kit/issues/131),
[#157](https://github.com/go-kit/kit/pull/157), [#157](https://github.com/go-kit/kit/pull/157),
[#164](https://github.com/go-kit/kit/issues/164), and [#164](https://github.com/go-kit/kit/issues/164), and
[#252](https://github.com/go-kit/kit/pull/252) [#252](https://github.com/go-kit/kit/pull/252)
to review historical conversations about package log and the Logger interface. to review historical conversations about package log and the Logger interface.
Value-add packages and suggestions, Value-add packages and suggestions,
like improvements to [the leveled logger](https://godoc.org/github.com/go-kit/kit/log/level), like improvements to [the leveled logger](https://godoc.org/github.com/go-kit/kit/log/levels),
are of course welcome. Good proposals should are of course welcome.
Good proposals should
- Be composable with [contextual loggers](https://godoc.org/github.com/go-kit/kit/log#With), - Be composable with [log.Context](https://godoc.org/github.com/go-kit/kit/log#Context),
- Not break the behavior of [log.Caller](https://godoc.org/github.com/go-kit/kit/log#Caller) in any wrapped contextual loggers, and - Not break the behavior of [log.Caller](https://godoc.org/github.com/go-kit/kit/log#Caller) in any wrapped context, and
- Be friendly to packages that accept only an unadorned log.Logger. - Be friendly to packages that accept only an unadorned log.Logger.
## Benchmarks & comparisons ## Benchmarks & comparisons

View File

@ -7,7 +7,7 @@ import (
) )
func benchmarkRunner(b *testing.B, logger log.Logger, f func(log.Logger)) { func benchmarkRunner(b *testing.B, logger log.Logger, f func(log.Logger)) {
lc := log.With(logger, "common_key", "common_value") lc := log.NewContext(logger).With("common_key", "common_value")
b.ReportAllocs() b.ReportAllocs()
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
@ -17,5 +17,5 @@ func benchmarkRunner(b *testing.B, logger log.Logger, f func(log.Logger)) {
var ( var (
baseMessage = func(logger log.Logger) { logger.Log("foo_key", "foo_value") } baseMessage = func(logger log.Logger) { logger.Log("foo_key", "foo_value") }
withMessage = func(logger log.Logger) { log.With(logger, "a", "b").Log("c", "d") } withMessage = func(logger log.Logger) { log.NewContext(logger).With("a", "b").Log("c", "d") }
) )

View File

@ -35,15 +35,14 @@
// idea to log simple values without formatting them. This practice allows // idea to log simple values without formatting them. This practice allows
// the chosen logger to encode values in the most appropriate way. // the chosen logger to encode values in the most appropriate way.
// //
// Contextual Loggers // Log Context
// //
// A contextual logger stores keyvals that it includes in all log events. // A log context stores keyvals that it includes in all log events. Building
// Building appropriate contextual loggers reduces repetition and aids // appropriate log contexts reduces repetition and aids consistency in the
// consistency in the resulting log output. With and WithPrefix add context to // resulting log output. We can use a context to improve the RunTask example.
// a logger. We can use With to improve the RunTask example.
// //
// func RunTask(task Task, logger log.Logger) string { // func RunTask(task Task, logger log.Logger) string {
// logger = log.With(logger, "taskID", task.ID) // logger = log.NewContext(logger).With("taskID", task.ID)
// logger.Log("event", "starting task") // logger.Log("event", "starting task")
// ... // ...
// taskHelper(task.Cmd, logger) // taskHelper(task.Cmd, logger)
@ -52,18 +51,19 @@
// } // }
// //
// The improved version emits the same log events as the original for the // The improved version emits the same log events as the original for the
// first and last calls to Log. Passing the contextual logger to taskHelper // first and last calls to Log. The call to taskHelper highlights that a
// enables each log event created by taskHelper to include the task.ID even // context may be passed as a logger to other functions. Each log event
// though taskHelper does not have access to that value. Using contextual // created by the called function will include the task.ID even though the
// loggers this way simplifies producing log output that enables tracing the // function does not have access to that value. Using log contexts this way
// life cycle of individual tasks. (See the Contextual example for the full // simplifies producing log output that enables tracing the life cycle of
// code of the above snippet.) // individual tasks. (See the Context example for the full code of the
// above snippet.)
// //
// Dynamic Contextual Values // Dynamic Context Values
// //
// A Valuer function stored in a contextual logger generates a new value each // A Valuer function stored in a log context generates a new value each time
// time an event is logged. The Valuer example demonstrates how this feature // the context logs an event. The Valuer example demonstrates how this
// works. // feature works.
// //
// Valuers provide the basis for consistently logging timestamps and source // Valuers provide the basis for consistently logging timestamps and source
// code location. The log package defines several valuers for that purpose. // code location. The log package defines several valuers for that purpose.
@ -72,7 +72,7 @@
// entries contain a timestamp and source location looks like this: // entries contain a timestamp and source location looks like this:
// //
// logger := log.NewLogfmtLogger(log.NewSyncWriter(os.Stdout)) // logger := log.NewLogfmtLogger(log.NewSyncWriter(os.Stdout))
// logger = log.With(logger, "ts", log.DefaultTimestampUTC, "caller", log.DefaultCaller) // logger = log.NewContext(logger).With("ts", log.DefaultTimestampUTC, "caller", log.DefaultCaller)
// //
// Concurrent Safety // Concurrent Safety
// //
@ -90,27 +90,4 @@
// handled atomically within the wrapped logger, but it typically serializes // handled atomically within the wrapped logger, but it typically serializes
// both the formatting and output logic. Use a SyncLogger if the formatting // both the formatting and output logic. Use a SyncLogger if the formatting
// logger may perform multiple writes per log event. // logger may perform multiple writes per log event.
//
// Error Handling
//
// This package relies on the practice of wrapping or decorating loggers with
// other loggers to provide composable pieces of functionality. It also means
// that Logger.Log must return an error because some
// implementations—especially those that output log data to an io.Writer—may
// encounter errors that cannot be handled locally. This in turn means that
// Loggers that wrap other loggers should return errors from the wrapped
// logger up the stack.
//
// Fortunately, the decorator pattern also provides a way to avoid the
// necessity to check for errors every time an application calls Logger.Log.
// An application required to panic whenever its Logger encounters
// an error could initialize its logger as follows.
//
// fmtlogger := log.NewLogfmtLogger(log.NewSyncWriter(os.Stdout))
// logger := log.LoggerFunc(func(keyvals ...interface{}) error {
// if err := fmtlogger.Log(keyvals...); err != nil {
// panic(err)
// }
// return nil
// })
package log package log

View File

@ -1,9 +1,7 @@
package log_test package log_test
import ( import (
"math/rand"
"os" "os"
"sync"
"time" "time"
"github.com/go-kit/kit/log" "github.com/go-kit/kit/log"
@ -29,7 +27,7 @@ func Example_basic() {
// taskID=1 event="task complete" // taskID=1 event="task complete"
} }
func Example_contextual() { func Example_context() {
logger := log.NewLogfmtLogger(os.Stdout) logger := log.NewLogfmtLogger(os.Stdout)
type Task struct { type Task struct {
@ -43,7 +41,7 @@ func Example_contextual() {
} }
RunTask := func(task Task, logger log.Logger) { RunTask := func(task Task, logger log.Logger) {
logger = log.With(logger, "taskID", task.ID) logger = log.NewContext(logger).With("taskID", task.ID)
logger.Log("event", "starting task") logger.Log("event", "starting task")
taskHelper(task.Cmd, logger) taskHelper(task.Cmd, logger)
@ -68,7 +66,7 @@ func Example_valuer() {
return count return count
} }
logger = log.With(logger, "count", log.Valuer(counter)) logger = log.NewContext(logger).With("count", log.Valuer(counter))
logger.Log("call", "first") logger.Log("call", "first")
logger.Log("call", "second") logger.Log("call", "second")
@ -88,7 +86,7 @@ func Example_debugInfo() {
return baseTime return baseTime
} }
logger = log.With(logger, "time", log.Timestamp(mockTime), "caller", log.DefaultCaller) logger = log.NewContext(logger).With("time", log.Timestamp(mockTime), "caller", log.DefaultCaller)
logger.Log("call", "first") logger.Log("call", "first")
logger.Log("call", "second") logger.Log("call", "second")
@ -98,40 +96,7 @@ func Example_debugInfo() {
logger.Log("call", "third") logger.Log("call", "third")
// Output: // Output:
// time=2015-02-03T10:00:01Z caller=example_test.go:93 call=first // time=2015-02-03T10:00:01Z caller=example_test.go:91 call=first
// time=2015-02-03T10:00:02Z caller=example_test.go:94 call=second // time=2015-02-03T10:00:02Z caller=example_test.go:92 call=second
// time=2015-02-03T10:00:03Z caller=example_test.go:98 call=third // time=2015-02-03T10:00:03Z caller=example_test.go:96 call=third
}
func Example_syncWriter() {
w := log.NewSyncWriter(os.Stdout)
logger := log.NewLogfmtLogger(w)
type Task struct {
ID int
}
var wg sync.WaitGroup
RunTask := func(task Task, logger log.Logger) {
logger.Log("taskID", task.ID, "event", "starting task")
time.Sleep(time.Duration(rand.Intn(200)) * time.Millisecond)
logger.Log("taskID", task.ID, "event", "task complete")
wg.Done()
}
wg.Add(2)
go RunTask(Task{ID: 1}, logger)
go RunTask(Task{ID: 2}, logger)
wg.Wait()
// Unordered output:
// taskID=1 event="starting task"
// taskID=2 event="starting task"
// taskID=1 event="task complete"
// taskID=2 event="task complete"
} }

View File

@ -0,0 +1,65 @@
package level_test
import (
"io/ioutil"
"testing"
"github.com/go-kit/kit/log"
"github.com/go-kit/kit/log/experimental_level"
)
func BenchmarkNopBaseline(b *testing.B) {
benchmarkRunner(b, log.NewNopLogger())
}
func BenchmarkNopDisallowedLevel(b *testing.B) {
benchmarkRunner(b, level.New(log.NewNopLogger(), level.Config{
Allowed: level.AllowInfoAndAbove(),
}))
}
func BenchmarkNopAllowedLevel(b *testing.B) {
benchmarkRunner(b, level.New(log.NewNopLogger(), level.Config{
Allowed: level.AllowAll(),
}))
}
func BenchmarkJSONBaseline(b *testing.B) {
benchmarkRunner(b, log.NewJSONLogger(ioutil.Discard))
}
func BenchmarkJSONDisallowedLevel(b *testing.B) {
benchmarkRunner(b, level.New(log.NewJSONLogger(ioutil.Discard), level.Config{
Allowed: level.AllowInfoAndAbove(),
}))
}
func BenchmarkJSONAllowedLevel(b *testing.B) {
benchmarkRunner(b, level.New(log.NewJSONLogger(ioutil.Discard), level.Config{
Allowed: level.AllowAll(),
}))
}
func BenchmarkLogfmtBaseline(b *testing.B) {
benchmarkRunner(b, log.NewLogfmtLogger(ioutil.Discard))
}
func BenchmarkLogfmtDisallowedLevel(b *testing.B) {
benchmarkRunner(b, level.New(log.NewLogfmtLogger(ioutil.Discard), level.Config{
Allowed: level.AllowInfoAndAbove(),
}))
}
func BenchmarkLogfmtAllowedLevel(b *testing.B) {
benchmarkRunner(b, level.New(log.NewLogfmtLogger(ioutil.Discard), level.Config{
Allowed: level.AllowAll(),
}))
}
func benchmarkRunner(b *testing.B, logger log.Logger) {
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
level.Debug(logger).Log("foo", "bar")
}
}

View File

@ -0,0 +1,27 @@
// Package level is an EXPERIMENTAL levelled logging package. The API will
// definitely have breaking changes and may be deleted altogether. Be warned!
//
// To use the level package, create a logger as per normal in your func main,
// and wrap it with level.New.
//
// var logger log.Logger
// logger = log.NewLogfmtLogger(os.Stderr)
// logger = level.New(logger, level.Config{Allowed: level.AllowInfoAndAbove}) // <--
// logger = log.NewContext(logger).With("ts", log.DefaultTimestampUTC)
//
// Then, at the callsites, use one of the level.Debug, Info, Warn, or Error
// helper methods to emit leveled log events.
//
// logger.Log("foo", "bar") // as normal, no level
// level.Debug(logger).Log("request_id", reqID, "trace_data", trace.Get())
// if value > 100 {
// level.Error(logger).Log("value", value)
// }
//
// The leveled logger allows precise control over what should happen if a log
// event is emitted without a level key, or if a squelched level is used. Check
// the Config struct for details. And, you can easily use non-default level
// values: create new string constants for whatever you want to change, pass
// them explicitly to the Config struct, and write your own level.Foo-style
// helper methods.
package level

View File

@ -0,0 +1,146 @@
package level
import (
"github.com/go-kit/kit/log"
)
var (
levelKey = "level"
errorLevelValue = "error"
warnLevelValue = "warn"
infoLevelValue = "info"
debugLevelValue = "debug"
)
// AllowAll is an alias for AllowDebugAndAbove.
func AllowAll() []string {
return AllowDebugAndAbove()
}
// AllowDebugAndAbove allows all of the four default log levels.
// Its return value may be provided as the Allowed parameter in the Config.
func AllowDebugAndAbove() []string {
return []string{errorLevelValue, warnLevelValue, infoLevelValue, debugLevelValue}
}
// AllowInfoAndAbove allows the default info, warn, and error log levels.
// Its return value may be provided as the Allowed parameter in the Config.
func AllowInfoAndAbove() []string {
return []string{errorLevelValue, warnLevelValue, infoLevelValue}
}
// AllowWarnAndAbove allows the default warn and error log levels.
// Its return value may be provided as the Allowed parameter in the Config.
func AllowWarnAndAbove() []string {
return []string{errorLevelValue, warnLevelValue}
}
// AllowErrorOnly allows only the default error log level.
// Its return value may be provided as the Allowed parameter in the Config.
func AllowErrorOnly() []string {
return []string{errorLevelValue}
}
// AllowNone allows none of the default log levels.
// Its return value may be provided as the Allowed parameter in the Config.
func AllowNone() []string {
return []string{}
}
// Error returns a logger with the level key set to ErrorLevelValue.
func Error(logger log.Logger) log.Logger {
return log.NewContext(logger).With(levelKey, errorLevelValue)
}
// Warn returns a logger with the level key set to WarnLevelValue.
func Warn(logger log.Logger) log.Logger {
return log.NewContext(logger).With(levelKey, warnLevelValue)
}
// Info returns a logger with the level key set to InfoLevelValue.
func Info(logger log.Logger) log.Logger {
return log.NewContext(logger).With(levelKey, infoLevelValue)
}
// Debug returns a logger with the level key set to DebugLevelValue.
func Debug(logger log.Logger) log.Logger {
return log.NewContext(logger).With(levelKey, debugLevelValue)
}
// Config parameterizes the leveled logger.
type Config struct {
// Allowed enumerates the accepted log levels. If a log event is encountered
// with a level key set to a value that isn't explicitly allowed, the event
// will be squelched, and ErrNotAllowed returned.
Allowed []string
// ErrNotAllowed is returned to the caller when Log is invoked with a level
// key that hasn't been explicitly allowed. By default, ErrNotAllowed is
// nil; in this case, the log event is squelched with no error.
ErrNotAllowed error
// SquelchNoLevel will squelch log events with no level key, so that they
// don't proceed through to the wrapped logger. If SquelchNoLevel is set to
// true and a log event is squelched in this way, ErrNoLevel is returned to
// the caller.
SquelchNoLevel bool
// ErrNoLevel is returned to the caller when SquelchNoLevel is true, and Log
// is invoked without a level key. By default, ErrNoLevel is nil; in this
// case, the log event is squelched with no error.
ErrNoLevel error
}
// New wraps the logger and implements level checking. See the commentary on the
// Config object for a detailed description of how to configure levels.
func New(next log.Logger, config Config) log.Logger {
return &logger{
next: next,
allowed: makeSet(config.Allowed),
errNotAllowed: config.ErrNotAllowed,
squelchNoLevel: config.SquelchNoLevel,
errNoLevel: config.ErrNoLevel,
}
}
type logger struct {
next log.Logger
allowed map[string]struct{}
errNotAllowed error
squelchNoLevel bool
errNoLevel error
}
func (l *logger) Log(keyvals ...interface{}) error {
var hasLevel, levelAllowed bool
for i := 0; i < len(keyvals); i += 2 {
if k, ok := keyvals[i].(string); !ok || k != levelKey {
continue
}
hasLevel = true
if i >= len(keyvals) {
continue
}
v, ok := keyvals[i+1].(string)
if !ok {
continue
}
_, levelAllowed = l.allowed[v]
break
}
if !hasLevel && l.squelchNoLevel {
return l.errNoLevel
}
if hasLevel && !levelAllowed {
return l.errNotAllowed
}
return l.next.Log(keyvals...)
}
func makeSet(a []string) map[string]struct{} {
m := make(map[string]struct{}, len(a))
for _, s := range a {
m[s] = struct{}{}
}
return m
}

View File

@ -0,0 +1,154 @@
package level_test
import (
"bytes"
"errors"
"strings"
"testing"
"github.com/go-kit/kit/log"
"github.com/go-kit/kit/log/experimental_level"
)
func TestVariousLevels(t *testing.T) {
for _, testcase := range []struct {
allowed []string
want string
}{
{
level.AllowAll(),
strings.Join([]string{
`{"level":"debug","this is":"debug log"}`,
`{"level":"info","this is":"info log"}`,
`{"level":"warn","this is":"warn log"}`,
`{"level":"error","this is":"error log"}`,
}, "\n"),
},
{
level.AllowDebugAndAbove(),
strings.Join([]string{
`{"level":"debug","this is":"debug log"}`,
`{"level":"info","this is":"info log"}`,
`{"level":"warn","this is":"warn log"}`,
`{"level":"error","this is":"error log"}`,
}, "\n"),
},
{
level.AllowInfoAndAbove(),
strings.Join([]string{
`{"level":"info","this is":"info log"}`,
`{"level":"warn","this is":"warn log"}`,
`{"level":"error","this is":"error log"}`,
}, "\n"),
},
{
level.AllowWarnAndAbove(),
strings.Join([]string{
`{"level":"warn","this is":"warn log"}`,
`{"level":"error","this is":"error log"}`,
}, "\n"),
},
{
level.AllowErrorOnly(),
strings.Join([]string{
`{"level":"error","this is":"error log"}`,
}, "\n"),
},
{
level.AllowNone(),
``,
},
} {
var buf bytes.Buffer
logger := level.New(log.NewJSONLogger(&buf), level.Config{Allowed: testcase.allowed})
level.Debug(logger).Log("this is", "debug log")
level.Info(logger).Log("this is", "info log")
level.Warn(logger).Log("this is", "warn log")
level.Error(logger).Log("this is", "error log")
if want, have := testcase.want, strings.TrimSpace(buf.String()); want != have {
t.Errorf("given Allowed=%v: want\n%s\nhave\n%s", testcase.allowed, want, have)
}
}
}
func TestErrNotAllowed(t *testing.T) {
myError := errors.New("squelched!")
logger := level.New(log.NewNopLogger(), level.Config{
Allowed: level.AllowWarnAndAbove(),
ErrNotAllowed: myError,
})
if want, have := myError, level.Info(logger).Log("foo", "bar"); want != have {
t.Errorf("want %#+v, have %#+v", want, have)
}
if want, have := error(nil), level.Warn(logger).Log("foo", "bar"); want != have {
t.Errorf("want %#+v, have %#+v", want, have)
}
}
func TestErrNoLevel(t *testing.T) {
myError := errors.New("no level specified")
var buf bytes.Buffer
logger := level.New(log.NewJSONLogger(&buf), level.Config{
SquelchNoLevel: true,
ErrNoLevel: myError,
})
if want, have := myError, logger.Log("foo", "bar"); want != have {
t.Errorf("want %v, have %v", want, have)
}
if want, have := ``, strings.TrimSpace(buf.String()); want != have {
t.Errorf("want %q, have %q", want, have)
}
}
func TestAllowNoLevel(t *testing.T) {
var buf bytes.Buffer
logger := level.New(log.NewJSONLogger(&buf), level.Config{
SquelchNoLevel: false,
ErrNoLevel: errors.New("I should never be returned!"),
})
if want, have := error(nil), logger.Log("foo", "bar"); want != have {
t.Errorf("want %v, have %v", want, have)
}
if want, have := `{"foo":"bar"}`, strings.TrimSpace(buf.String()); want != have {
t.Errorf("want %q, have %q", want, have)
}
}
func TestLevelContext(t *testing.T) {
var buf bytes.Buffer
// Wrapping the level logger with a context allows users to use
// log.DefaultCaller as per normal.
var logger log.Logger
logger = log.NewLogfmtLogger(&buf)
logger = level.New(logger, level.Config{Allowed: level.AllowAll()})
logger = log.NewContext(logger).With("caller", log.DefaultCaller)
level.Info(logger).Log("foo", "bar")
if want, have := `caller=level_test.go:134 level=info foo=bar`, strings.TrimSpace(buf.String()); want != have {
t.Errorf("want %q, have %q", want, have)
}
}
func TestContextLevel(t *testing.T) {
var buf bytes.Buffer
// Wrapping a context with the level logger still works, but requires users
// to specify a higher callstack depth value.
var logger log.Logger
logger = log.NewLogfmtLogger(&buf)
logger = log.NewContext(logger).With("caller", log.Caller(5))
logger = level.New(logger, level.Config{Allowed: level.AllowAll()})
level.Info(logger).Log("foo", "bar")
if want, have := `caller=level_test.go:150 level=info foo=bar`, strings.TrimSpace(buf.String()); want != have {
t.Errorf("want %q, have %q", want, have)
}
}

View File

@ -13,7 +13,7 @@ func TestJSONLoggerCaller(t *testing.T) {
t.Parallel() t.Parallel()
buf := &bytes.Buffer{} buf := &bytes.Buffer{}
logger := log.NewJSONLogger(buf) logger := log.NewJSONLogger(buf)
logger = log.With(logger, "caller", log.DefaultCaller) logger = log.NewContext(logger).With("caller", log.DefaultCaller)
if err := logger.Log(); err != nil { if err := logger.Log(); err != nil {
t.Fatal(err) t.Fatal(err)

View File

@ -1,72 +0,0 @@
package level_test
import (
"io/ioutil"
"testing"
"github.com/go-kit/kit/log"
"github.com/go-kit/kit/log/level"
)
func Benchmark(b *testing.B) {
contexts := []struct {
name string
context func(log.Logger) log.Logger
}{
{"NoContext", func(l log.Logger) log.Logger {
return l
}},
{"TimeContext", func(l log.Logger) log.Logger {
return log.With(l, "time", log.DefaultTimestampUTC)
}},
{"CallerContext", func(l log.Logger) log.Logger {
return log.With(l, "caller", log.DefaultCaller)
}},
{"TimeCallerReqIDContext", func(l log.Logger) log.Logger {
return log.With(l, "time", log.DefaultTimestampUTC, "caller", log.DefaultCaller, "reqID", 29)
}},
}
loggers := []struct {
name string
logger log.Logger
}{
{"Nop", log.NewNopLogger()},
{"Logfmt", log.NewLogfmtLogger(ioutil.Discard)},
{"JSON", log.NewJSONLogger(ioutil.Discard)},
}
filters := []struct {
name string
filter func(log.Logger) log.Logger
}{
{"Baseline", func(l log.Logger) log.Logger {
return l
}},
{"DisallowedLevel", func(l log.Logger) log.Logger {
return level.NewFilter(l, level.AllowInfo())
}},
{"AllowedLevel", func(l log.Logger) log.Logger {
return level.NewFilter(l, level.AllowAll())
}},
}
for _, c := range contexts {
b.Run(c.name, func(b *testing.B) {
for _, f := range filters {
b.Run(f.name, func(b *testing.B) {
for _, l := range loggers {
b.Run(l.name, func(b *testing.B) {
logger := c.context(f.filter(l.logger))
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
level.Debug(logger).Log("foo", "bar")
}
})
}
})
}
})
}
}

View File

@ -1,22 +0,0 @@
// Package level implements leveled logging on top of package log. To use the
// level package, create a logger as per normal in your func main, and wrap it
// with level.NewFilter.
//
// var logger log.Logger
// logger = log.NewLogfmtLogger(os.Stderr)
// logger = level.NewFilter(logger, level.AllowInfoAndAbove()) // <--
// logger = log.NewContext(logger).With("ts", log.DefaultTimestampUTC)
//
// Then, at the callsites, use one of the level.Debug, Info, Warn, or Error
// helper methods to emit leveled log events.
//
// logger.Log("foo", "bar") // as normal, no level
// level.Debug(logger).Log("request_id", reqID, "trace_data", trace.Get())
// if value > 100 {
// level.Error(logger).Log("value", value)
// }
//
// NewFilter allows precise control over what happens when a log event is
// emitted without a level key, or if a squelched level is used. Check the
// Option functions for details.
package level

View File

@ -1,25 +0,0 @@
package level_test
import (
"errors"
"os"
"github.com/go-kit/kit/log"
"github.com/go-kit/kit/log/level"
)
func Example_basic() {
// setup logger with level filter
logger := log.NewLogfmtLogger(os.Stdout)
logger = level.NewFilter(logger, level.AllowInfo())
logger = log.With(logger, "caller", log.DefaultCaller)
// use level helpers to log at different levels
level.Error(logger).Log("err", errors.New("bad data"))
level.Info(logger).Log("event", "data saved")
level.Debug(logger).Log("next item", 17) // filtered
// Output:
// level=error caller=example_test.go:18 err="bad data"
// level=info caller=example_test.go:19 event="data saved"
}

View File

@ -1,205 +0,0 @@
package level
import "github.com/go-kit/kit/log"
// Error returns a logger that includes a Key/ErrorValue pair.
func Error(logger log.Logger) log.Logger {
return log.WithPrefix(logger, Key(), ErrorValue())
}
// Warn returns a logger that includes a Key/WarnValue pair.
func Warn(logger log.Logger) log.Logger {
return log.WithPrefix(logger, Key(), WarnValue())
}
// Info returns a logger that includes a Key/InfoValue pair.
func Info(logger log.Logger) log.Logger {
return log.WithPrefix(logger, Key(), InfoValue())
}
// Debug returns a logger that includes a Key/DebugValue pair.
func Debug(logger log.Logger) log.Logger {
return log.WithPrefix(logger, Key(), DebugValue())
}
// NewFilter wraps next and implements level filtering. See the commentary on
// the Option functions for a detailed description of how to configure levels.
// If no options are provided, all leveled log events created with Debug,
// Info, Warn or Error helper methods are squelched and non-leveled log
// events are passed to next unmodified.
func NewFilter(next log.Logger, options ...Option) log.Logger {
l := &logger{
next: next,
}
for _, option := range options {
option(l)
}
return l
}
type logger struct {
next log.Logger
allowed level
squelchNoLevel bool
errNotAllowed error
errNoLevel error
}
func (l *logger) Log(keyvals ...interface{}) error {
var hasLevel, levelAllowed bool
for i := 1; i < len(keyvals); i += 2 {
if v, ok := keyvals[i].(*levelValue); ok {
hasLevel = true
levelAllowed = l.allowed&v.level != 0
break
}
}
if !hasLevel && l.squelchNoLevel {
return l.errNoLevel
}
if hasLevel && !levelAllowed {
return l.errNotAllowed
}
return l.next.Log(keyvals...)
}
// Option sets a parameter for the leveled logger.
type Option func(*logger)
// AllowAll is an alias for AllowDebug.
func AllowAll() Option {
return AllowDebug()
}
// AllowDebug allows error, warn, info and debug level log events to pass.
func AllowDebug() Option {
return allowed(levelError | levelWarn | levelInfo | levelDebug)
}
// AllowInfo allows error, warn and info level log events to pass.
func AllowInfo() Option {
return allowed(levelError | levelWarn | levelInfo)
}
// AllowWarn allows error and warn level log events to pass.
func AllowWarn() Option {
return allowed(levelError | levelWarn)
}
// AllowError allows only error level log events to pass.
func AllowError() Option {
return allowed(levelError)
}
// AllowNone allows no leveled log events to pass.
func AllowNone() Option {
return allowed(0)
}
func allowed(allowed level) Option {
return func(l *logger) { l.allowed = allowed }
}
// ErrNotAllowed sets the error to return from Log when it squelches a log
// event disallowed by the configured Allow[Level] option. By default,
// ErrNotAllowed is nil; in this case the log event is squelched with no
// error.
func ErrNotAllowed(err error) Option {
return func(l *logger) { l.errNotAllowed = err }
}
// SquelchNoLevel instructs Log to squelch log events with no level, so that
// they don't proceed through to the wrapped logger. If SquelchNoLevel is set
// to true and a log event is squelched in this way, the error value
// configured with ErrNoLevel is returned to the caller.
func SquelchNoLevel(squelch bool) Option {
return func(l *logger) { l.squelchNoLevel = squelch }
}
// ErrNoLevel sets the error to return from Log when it squelches a log event
// with no level. By default, ErrNoLevel is nil; in this case the log event is
// squelched with no error.
func ErrNoLevel(err error) Option {
return func(l *logger) { l.errNoLevel = err }
}
// NewInjector wraps next and returns a logger that adds a Key/level pair to
// the beginning of log events that don't already contain a level. In effect,
// this gives a default level to logs without a level.
func NewInjector(next log.Logger, level Value) log.Logger {
return &injector{
next: next,
level: level,
}
}
type injector struct {
next log.Logger
level interface{}
}
func (l *injector) Log(keyvals ...interface{}) error {
for i := 1; i < len(keyvals); i += 2 {
if _, ok := keyvals[i].(*levelValue); ok {
return l.next.Log(keyvals...)
}
}
kvs := make([]interface{}, len(keyvals)+2)
kvs[0], kvs[1] = key, l.level
copy(kvs[2:], keyvals)
return l.next.Log(kvs...)
}
// Value is the interface that each of the canonical level values implement.
// It contains unexported methods that prevent types from other packages from
// implementing it and guaranteeing that NewFilter can distinguish the levels
// defined in this package from all other values.
type Value interface {
String() string
levelVal()
}
// Key returns the unique key added to log events by the loggers in this
// package.
func Key() interface{} { return key }
// ErrorValue returns the unique value added to log events by Error.
func ErrorValue() Value { return errorValue }
// WarnValue returns the unique value added to log events by Warn.
func WarnValue() Value { return warnValue }
// InfoValue returns the unique value added to log events by Info.
func InfoValue() Value { return infoValue }
// DebugValue returns the unique value added to log events by Warn.
func DebugValue() Value { return debugValue }
var (
// key is of type interfae{} so that it allocates once during package
// initialization and avoids allocating every type the value is added to a
// []interface{} later.
key interface{} = "level"
errorValue = &levelValue{level: levelError, name: "error"}
warnValue = &levelValue{level: levelWarn, name: "warn"}
infoValue = &levelValue{level: levelInfo, name: "info"}
debugValue = &levelValue{level: levelDebug, name: "debug"}
)
type level byte
const (
levelDebug level = 1 << iota
levelInfo
levelWarn
levelError
)
type levelValue struct {
name string
level
}
func (v *levelValue) String() string { return v.name }
func (v *levelValue) levelVal() {}

View File

@ -1,235 +0,0 @@
package level_test
import (
"bytes"
"errors"
"io"
"strings"
"testing"
"github.com/go-kit/kit/log"
"github.com/go-kit/kit/log/level"
)
func TestVariousLevels(t *testing.T) {
testCases := []struct {
name string
allowed level.Option
want string
}{
{
"AllowAll",
level.AllowAll(),
strings.Join([]string{
`{"level":"debug","this is":"debug log"}`,
`{"level":"info","this is":"info log"}`,
`{"level":"warn","this is":"warn log"}`,
`{"level":"error","this is":"error log"}`,
}, "\n"),
},
{
"AllowDebug",
level.AllowDebug(),
strings.Join([]string{
`{"level":"debug","this is":"debug log"}`,
`{"level":"info","this is":"info log"}`,
`{"level":"warn","this is":"warn log"}`,
`{"level":"error","this is":"error log"}`,
}, "\n"),
},
{
"AllowDebug",
level.AllowInfo(),
strings.Join([]string{
`{"level":"info","this is":"info log"}`,
`{"level":"warn","this is":"warn log"}`,
`{"level":"error","this is":"error log"}`,
}, "\n"),
},
{
"AllowWarn",
level.AllowWarn(),
strings.Join([]string{
`{"level":"warn","this is":"warn log"}`,
`{"level":"error","this is":"error log"}`,
}, "\n"),
},
{
"AllowError",
level.AllowError(),
strings.Join([]string{
`{"level":"error","this is":"error log"}`,
}, "\n"),
},
{
"AllowNone",
level.AllowNone(),
``,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
var buf bytes.Buffer
logger := level.NewFilter(log.NewJSONLogger(&buf), tc.allowed)
level.Debug(logger).Log("this is", "debug log")
level.Info(logger).Log("this is", "info log")
level.Warn(logger).Log("this is", "warn log")
level.Error(logger).Log("this is", "error log")
if want, have := tc.want, strings.TrimSpace(buf.String()); want != have {
t.Errorf("\nwant:\n%s\nhave:\n%s", want, have)
}
})
}
}
func TestErrNotAllowed(t *testing.T) {
myError := errors.New("squelched!")
opts := []level.Option{
level.AllowWarn(),
level.ErrNotAllowed(myError),
}
logger := level.NewFilter(log.NewNopLogger(), opts...)
if want, have := myError, level.Info(logger).Log("foo", "bar"); want != have {
t.Errorf("want %#+v, have %#+v", want, have)
}
if want, have := error(nil), level.Warn(logger).Log("foo", "bar"); want != have {
t.Errorf("want %#+v, have %#+v", want, have)
}
}
func TestErrNoLevel(t *testing.T) {
myError := errors.New("no level specified")
var buf bytes.Buffer
opts := []level.Option{
level.SquelchNoLevel(true),
level.ErrNoLevel(myError),
}
logger := level.NewFilter(log.NewJSONLogger(&buf), opts...)
if want, have := myError, logger.Log("foo", "bar"); want != have {
t.Errorf("want %v, have %v", want, have)
}
if want, have := ``, strings.TrimSpace(buf.String()); want != have {
t.Errorf("\nwant '%s'\nhave '%s'", want, have)
}
}
func TestAllowNoLevel(t *testing.T) {
var buf bytes.Buffer
opts := []level.Option{
level.SquelchNoLevel(false),
level.ErrNoLevel(errors.New("I should never be returned!")),
}
logger := level.NewFilter(log.NewJSONLogger(&buf), opts...)
if want, have := error(nil), logger.Log("foo", "bar"); want != have {
t.Errorf("want %v, have %v", want, have)
}
if want, have := `{"foo":"bar"}`, strings.TrimSpace(buf.String()); want != have {
t.Errorf("\nwant '%s'\nhave '%s'", want, have)
}
}
func TestLevelContext(t *testing.T) {
var buf bytes.Buffer
// Wrapping the level logger with a context allows users to use
// log.DefaultCaller as per normal.
var logger log.Logger
logger = log.NewLogfmtLogger(&buf)
logger = level.NewFilter(logger, level.AllowAll())
logger = log.With(logger, "caller", log.DefaultCaller)
level.Info(logger).Log("foo", "bar")
if want, have := `level=info caller=level_test.go:149 foo=bar`, strings.TrimSpace(buf.String()); want != have {
t.Errorf("\nwant '%s'\nhave '%s'", want, have)
}
}
func TestContextLevel(t *testing.T) {
var buf bytes.Buffer
// Wrapping a context with the level logger still works, but requires users
// to specify a higher callstack depth value.
var logger log.Logger
logger = log.NewLogfmtLogger(&buf)
logger = log.With(logger, "caller", log.Caller(5))
logger = level.NewFilter(logger, level.AllowAll())
level.Info(logger).Log("foo", "bar")
if want, have := `caller=level_test.go:165 level=info foo=bar`, strings.TrimSpace(buf.String()); want != have {
t.Errorf("\nwant '%s'\nhave '%s'", want, have)
}
}
func TestLevelFormatting(t *testing.T) {
testCases := []struct {
name string
format func(io.Writer) log.Logger
output string
}{
{
name: "logfmt",
format: log.NewLogfmtLogger,
output: `level=info foo=bar`,
},
{
name: "JSON",
format: log.NewJSONLogger,
output: `{"foo":"bar","level":"info"}`,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
var buf bytes.Buffer
logger := tc.format(&buf)
level.Info(logger).Log("foo", "bar")
if want, have := tc.output, strings.TrimSpace(buf.String()); want != have {
t.Errorf("\nwant: '%s'\nhave '%s'", want, have)
}
})
}
}
func TestInjector(t *testing.T) {
var (
output []interface{}
logger log.Logger
)
logger = log.LoggerFunc(func(keyvals ...interface{}) error {
output = keyvals
return nil
})
logger = level.NewInjector(logger, level.InfoValue())
logger.Log("foo", "bar")
if got, want := len(output), 4; got != want {
t.Errorf("missing level not injected: got len==%d, want len==%d", got, want)
}
if got, want := output[0], level.Key(); got != want {
t.Errorf("wrong level key: got %#v, want %#v", got, want)
}
if got, want := output[1], level.InfoValue(); got != want {
t.Errorf("wrong level value: got %#v, want %#v", got, want)
}
level.Error(logger).Log("foo", "bar")
if got, want := len(output), 4; got != want {
t.Errorf("leveled record modified: got len==%d, want len==%d", got, want)
}
if got, want := output[0], level.Key(); got != want {
t.Errorf("wrong level key: got %#v, want %#v", got, want)
}
if got, want := output[1], level.ErrorValue(); got != want {
t.Errorf("wrong level value: got %#v, want %#v", got, want)
}
}

View File

@ -7,7 +7,7 @@ import "github.com/go-kit/kit/log"
// want a different set of levels, you can create your own levels type very // want a different set of levels, you can create your own levels type very
// easily, and you can elide the configuration. // easily, and you can elide the configuration.
type Levels struct { type Levels struct {
logger log.Logger ctx *log.Context
levelKey string levelKey string
// We have a choice between storing level values in string fields or // We have a choice between storing level values in string fields or
@ -34,7 +34,7 @@ type Levels struct {
// New creates a new leveled logger, wrapping the passed logger. // New creates a new leveled logger, wrapping the passed logger.
func New(logger log.Logger, options ...Option) Levels { func New(logger log.Logger, options ...Option) Levels {
l := Levels{ l := Levels{
logger: logger, ctx: log.NewContext(logger),
levelKey: "level", levelKey: "level",
debugValue: "debug", debugValue: "debug",
@ -52,7 +52,7 @@ func New(logger log.Logger, options ...Option) Levels {
// With returns a new leveled logger that includes keyvals in all log events. // With returns a new leveled logger that includes keyvals in all log events.
func (l Levels) With(keyvals ...interface{}) Levels { func (l Levels) With(keyvals ...interface{}) Levels {
return Levels{ return Levels{
logger: log.With(l.logger, keyvals...), ctx: l.ctx.With(keyvals...),
levelKey: l.levelKey, levelKey: l.levelKey,
debugValue: l.debugValue, debugValue: l.debugValue,
infoValue: l.infoValue, infoValue: l.infoValue,
@ -64,27 +64,27 @@ func (l Levels) With(keyvals ...interface{}) Levels {
// Debug returns a debug level logger. // Debug returns a debug level logger.
func (l Levels) Debug() log.Logger { func (l Levels) Debug() log.Logger {
return log.WithPrefix(l.logger, l.levelKey, l.debugValue) return l.ctx.WithPrefix(l.levelKey, l.debugValue)
} }
// Info returns an info level logger. // Info returns an info level logger.
func (l Levels) Info() log.Logger { func (l Levels) Info() log.Logger {
return log.WithPrefix(l.logger, l.levelKey, l.infoValue) return l.ctx.WithPrefix(l.levelKey, l.infoValue)
} }
// Warn returns a warning level logger. // Warn returns a warning level logger.
func (l Levels) Warn() log.Logger { func (l Levels) Warn() log.Logger {
return log.WithPrefix(l.logger, l.levelKey, l.warnValue) return l.ctx.WithPrefix(l.levelKey, l.warnValue)
} }
// Error returns an error level logger. // Error returns an error level logger.
func (l Levels) Error() log.Logger { func (l Levels) Error() log.Logger {
return log.WithPrefix(l.logger, l.levelKey, l.errorValue) return l.ctx.WithPrefix(l.levelKey, l.errorValue)
} }
// Crit returns a critical level logger. // Crit returns a critical level logger.
func (l Levels) Crit() log.Logger { func (l Levels) Crit() log.Logger {
return log.WithPrefix(l.logger, l.levelKey, l.critValue) return l.ctx.WithPrefix(l.levelKey, l.critValue)
} }
// Option sets a parameter for leveled loggers. // Option sets a parameter for leveled loggers.

View File

@ -6,7 +6,7 @@ import (
"testing" "testing"
"github.com/go-kit/kit/log" "github.com/go-kit/kit/log"
levels "github.com/go-kit/kit/log/deprecated_levels" "github.com/go-kit/kit/log/levels"
) )
func TestDefaultLevels(t *testing.T) { func TestDefaultLevels(t *testing.T) {

View File

@ -6,7 +6,7 @@ import "errors"
// log event from keyvals, a variadic sequence of alternating keys and values. // log event from keyvals, a variadic sequence of alternating keys and values.
// Implementations must be safe for concurrent use by multiple goroutines. In // Implementations must be safe for concurrent use by multiple goroutines. In
// particular, any implementation of Logger that appends to keyvals or // particular, any implementation of Logger that appends to keyvals or
// modifies or retains any of its elements must make a copy first. // modifies any of its elements must make a copy first.
type Logger interface { type Logger interface {
Log(keyvals ...interface{}) error Log(keyvals ...interface{}) error
} }
@ -15,100 +15,62 @@ type Logger interface {
// the missing value. // the missing value.
var ErrMissingValue = errors.New("(MISSING)") var ErrMissingValue = errors.New("(MISSING)")
// With returns a new contextual logger with keyvals prepended to those passed // NewContext returns a new Context that logs to logger.
// to calls to Log. If logger is also a contextual logger created by With or func NewContext(logger Logger) *Context {
// WithPrefix, keyvals is appended to the existing context. if c, ok := logger.(*Context); ok {
// return c
// The returned Logger replaces all value elements (odd indexes) containing a
// Valuer with their generated value for each call to its Log method.
func With(logger Logger, keyvals ...interface{}) Logger {
if len(keyvals) == 0 {
return logger
}
l := newContext(logger)
kvs := append(l.keyvals, keyvals...)
if len(kvs)%2 != 0 {
kvs = append(kvs, ErrMissingValue)
}
return &context{
logger: l.logger,
// Limiting the capacity of the stored keyvals ensures that a new
// backing array is created if the slice must grow in Log or With.
// Using the extra capacity without copying risks a data race that
// would violate the Logger interface contract.
keyvals: kvs[:len(kvs):len(kvs)],
hasValuer: l.hasValuer || containsValuer(keyvals),
} }
return &Context{logger: logger}
} }
// WithPrefix returns a new contextual logger with keyvals prepended to those // Context must always have the same number of stack frames between calls to
// passed to calls to Log. If logger is also a contextual logger created by
// With or WithPrefix, keyvals is prepended to the existing context.
//
// The returned Logger replaces all value elements (odd indexes) containing a
// Valuer with their generated value for each call to its Log method.
func WithPrefix(logger Logger, keyvals ...interface{}) Logger {
if len(keyvals) == 0 {
return logger
}
l := newContext(logger)
// Limiting the capacity of the stored keyvals ensures that a new
// backing array is created if the slice must grow in Log or With.
// Using the extra capacity without copying risks a data race that
// would violate the Logger interface contract.
n := len(l.keyvals) + len(keyvals)
if len(keyvals)%2 != 0 {
n++
}
kvs := make([]interface{}, 0, n)
kvs = append(kvs, keyvals...)
if len(kvs)%2 != 0 {
kvs = append(kvs, ErrMissingValue)
}
kvs = append(kvs, l.keyvals...)
return &context{
logger: l.logger,
keyvals: kvs,
hasValuer: l.hasValuer || containsValuer(keyvals),
}
}
// context is the Logger implementation returned by With and WithPrefix. It
// wraps a Logger and holds keyvals that it includes in all log events. Its
// Log method calls bindValues to generate values for each Valuer in the
// context keyvals.
//
// A context must always have the same number of stack frames between calls to
// its Log method and the eventual binding of Valuers to their value. This // its Log method and the eventual binding of Valuers to their value. This
// requirement comes from the functional requirement to allow a context to // requirement comes from the functional requirement to allow a context to
// resolve application call site information for a Caller stored in the // resolve application call site information for a log.Caller stored in the
// context. To do this we must be able to predict the number of logging // context. To do this we must be able to predict the number of logging
// functions on the stack when bindValues is called. // functions on the stack when bindValues is called.
// //
// Two implementation details provide the needed stack depth consistency. // Three implementation details provide the needed stack depth consistency.
// The first two of these details also result in better amortized performance,
// and thus make sense even without the requirements regarding stack depth.
// The third detail, however, is subtle and tied to the implementation of the
// Go compiler.
// //
// 1. newContext avoids introducing an additional layer when asked to // 1. NewContext avoids introducing an additional layer when asked to
// wrap another context. // wrap another Context.
// 2. With and WithPrefix avoid introducing an additional layer by // 2. With avoids introducing an additional layer by returning a newly
// returning a newly constructed context with a merged keyvals rather // constructed Context with a merged keyvals rather than simply
// than simply wrapping the existing context. // wrapping the existing Context.
type context struct { // 3. All of Context's methods take pointer receivers even though they
// do not mutate the Context.
//
// Before explaining the last detail, first some background. The Go compiler
// generates wrapper methods to implement the auto dereferencing behavior when
// calling a value method through a pointer variable. These wrapper methods
// are also used when calling a value method through an interface variable
// because interfaces store a pointer to the underlying concrete value.
// Calling a pointer receiver through an interface does not require generating
// an additional function.
//
// If Context had value methods then calling Context.Log through a variable
// with type Logger would have an extra stack frame compared to calling
// Context.Log through a variable with type Context. Using pointer receivers
// avoids this problem.
// A Context wraps a Logger and holds keyvals that it includes in all log
// events. When logging, a Context replaces all value elements (odd indexes)
// containing a Valuer with their generated value for each call to its Log
// method.
type Context struct {
logger Logger logger Logger
keyvals []interface{} keyvals []interface{}
hasValuer bool hasValuer bool
} }
func newContext(logger Logger) *context {
if c, ok := logger.(*context); ok {
return c
}
return &context{logger: logger}
}
// Log replaces all value elements (odd indexes) containing a Valuer in the // Log replaces all value elements (odd indexes) containing a Valuer in the
// stored context with their generated value, appends keyvals, and passes the // stored context with their generated value, appends keyvals, and passes the
// result to the wrapped Logger. // result to the wrapped Logger.
func (l *context) Log(keyvals ...interface{}) error { func (l *Context) Log(keyvals ...interface{}) error {
kvs := append(l.keyvals, keyvals...) kvs := append(l.keyvals, keyvals...)
if len(kvs)%2 != 0 { if len(kvs)%2 != 0 {
kvs = append(kvs, ErrMissingValue) kvs = append(kvs, ErrMissingValue)
@ -124,6 +86,53 @@ func (l *context) Log(keyvals ...interface{}) error {
return l.logger.Log(kvs...) return l.logger.Log(kvs...)
} }
// With returns a new Context with keyvals appended to those of the receiver.
func (l *Context) With(keyvals ...interface{}) *Context {
if len(keyvals) == 0 {
return l
}
kvs := append(l.keyvals, keyvals...)
if len(kvs)%2 != 0 {
kvs = append(kvs, ErrMissingValue)
}
return &Context{
logger: l.logger,
// Limiting the capacity of the stored keyvals ensures that a new
// backing array is created if the slice must grow in Log or With.
// Using the extra capacity without copying risks a data race that
// would violate the Logger interface contract.
keyvals: kvs[:len(kvs):len(kvs)],
hasValuer: l.hasValuer || containsValuer(keyvals),
}
}
// WithPrefix returns a new Context with keyvals prepended to those of the
// receiver.
func (l *Context) WithPrefix(keyvals ...interface{}) *Context {
if len(keyvals) == 0 {
return l
}
// Limiting the capacity of the stored keyvals ensures that a new
// backing array is created if the slice must grow in Log or With.
// Using the extra capacity without copying risks a data race that
// would violate the Logger interface contract.
n := len(l.keyvals) + len(keyvals)
if len(keyvals)%2 != 0 {
n++
}
kvs := make([]interface{}, 0, n)
kvs = append(kvs, keyvals...)
if len(kvs)%2 != 0 {
kvs = append(kvs, ErrMissingValue)
}
kvs = append(kvs, l.keyvals...)
return &Context{
logger: l.logger,
keyvals: kvs,
hasValuer: l.hasValuer || containsValuer(keyvals),
}
}
// LoggerFunc is an adapter to allow use of ordinary functions as Loggers. If // LoggerFunc is an adapter to allow use of ordinary functions as Loggers. If
// f is a function with the appropriate signature, LoggerFunc(f) is a Logger // f is a function with the appropriate signature, LoggerFunc(f) is a Logger
// object that calls f. // object that calls f.

View File

@ -16,10 +16,10 @@ func TestContext(t *testing.T) {
logger := log.NewLogfmtLogger(buf) logger := log.NewLogfmtLogger(buf)
kvs := []interface{}{"a", 123} kvs := []interface{}{"a", 123}
lc := log.With(logger, kvs...) lc := log.NewContext(logger).With(kvs...)
kvs[1] = 0 // With should copy its key values kvs[1] = 0 // With should copy its key values
lc = log.With(lc, "b", "c") // With should stack lc = lc.With("b", "c") // With should stack
if err := lc.Log("msg", "message"); err != nil { if err := lc.Log("msg", "message"); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -28,7 +28,7 @@ func TestContext(t *testing.T) {
} }
buf.Reset() buf.Reset()
lc = log.WithPrefix(lc, "p", "first") lc = lc.WithPrefix("p", "first")
if err := lc.Log("msg", "message"); err != nil { if err := lc.Log("msg", "message"); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -45,7 +45,17 @@ func TestContextMissingValue(t *testing.T) {
return nil return nil
})) }))
log.WithPrefix(log.With(logger, "k1"), "k0").Log("k2") lc := log.NewContext(logger)
lc.Log("k")
if want, have := 2, len(output); want != have {
t.Errorf("want len(output) == %v, have %v", want, have)
}
if want, have := log.ErrMissingValue, output[1]; want != have {
t.Errorf("want %#v, have %#v", want, have)
}
lc.With("k1").WithPrefix("k0").Log("k2")
if want, have := 6, len(output); want != have { if want, have := 6, len(output); want != have {
t.Errorf("want len(output) == %v, have %v", want, have) t.Errorf("want len(output) == %v, have %v", want, have)
} }
@ -56,8 +66,10 @@ func TestContextMissingValue(t *testing.T) {
} }
} }
// Test that context.Log has a consistent function stack depth when binding // Test that Context.Log has a consistent function stack depth when binding
// Valuers, regardless of how many times With has been called. // log.Valuers, regardless of how many times Context.With has been called or
// whether Context.Log is called via an interface typed variable or a concrete
// typed variable.
func TestContextStackDepth(t *testing.T) { func TestContextStackDepth(t *testing.T) {
t.Parallel() t.Parallel()
fn := fmt.Sprintf("%n", stack.Caller(0)) fn := fmt.Sprintf("%n", stack.Caller(0))
@ -79,25 +91,32 @@ func TestContextStackDepth(t *testing.T) {
return nil return nil
}) })
logger = log.With(logger, "stack", stackValuer) concrete := log.NewContext(logger).With("stack", stackValuer)
var iface log.Logger = concrete
// Call through interface to get baseline. // Call through interface to get baseline.
logger.Log("k", "v") iface.Log("k", "v")
want := output[1].(int) want := output[1].(int)
for len(output) < 10 { for len(output) < 10 {
logger.Log("k", "v") concrete.Log("k", "v")
if have := output[1]; have != want { if have := output[1]; have != want {
t.Errorf("%d Withs: have %v, want %v", len(output)/2-1, have, want) t.Errorf("%d Withs: have %v, want %v", len(output)/2-1, have, want)
} }
wrapped := log.With(logger) iface.Log("k", "v")
if have := output[1]; have != want {
t.Errorf("%d Withs: have %v, want %v", len(output)/2-1, have, want)
}
wrapped := log.NewContext(concrete)
wrapped.Log("k", "v") wrapped.Log("k", "v")
if have := output[1]; have != want { if have := output[1]; have != want {
t.Errorf("%d Withs: have %v, want %v", len(output)/2-1, have, want) t.Errorf("%d Withs: have %v, want %v", len(output)/2-1, have, want)
} }
logger = log.With(logger, "k", "v") concrete = concrete.With("k", "v")
iface = concrete
} }
} }
@ -121,7 +140,7 @@ func TestWithConcurrent(t *testing.T) {
// With must be careful about handling slices that can grow without // With must be careful about handling slices that can grow without
// copying the underlying array, so give it a challenge. // copying the underlying array, so give it a challenge.
l := log.With(logger, make([]interface{}, 0, 2)...) l := log.NewContext(logger).With(make([]interface{}, 0, 2)...)
// Start logging concurrently. Each goroutine logs its id so the logger // Start logging concurrently. Each goroutine logs its id so the logger
// can bucket the event counts. // can bucket the event counts.
@ -156,7 +175,7 @@ func BenchmarkDiscard(b *testing.B) {
func BenchmarkOneWith(b *testing.B) { func BenchmarkOneWith(b *testing.B) {
logger := log.NewNopLogger() logger := log.NewNopLogger()
lc := log.With(logger, "k", "v") lc := log.NewContext(logger).With("k", "v")
b.ReportAllocs() b.ReportAllocs()
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
@ -166,9 +185,9 @@ func BenchmarkOneWith(b *testing.B) {
func BenchmarkTwoWith(b *testing.B) { func BenchmarkTwoWith(b *testing.B) {
logger := log.NewNopLogger() logger := log.NewNopLogger()
lc := log.With(logger, "k", "v") lc := log.NewContext(logger).With("k", "v")
for i := 1; i < 2; i++ { for i := 1; i < 2; i++ {
lc = log.With(lc, "k", "v") lc = lc.With("k", "v")
} }
b.ReportAllocs() b.ReportAllocs()
b.ResetTimer() b.ResetTimer()
@ -179,9 +198,9 @@ func BenchmarkTwoWith(b *testing.B) {
func BenchmarkTenWith(b *testing.B) { func BenchmarkTenWith(b *testing.B) {
logger := log.NewNopLogger() logger := log.NewNopLogger()
lc := log.With(logger, "k", "v") lc := log.NewContext(logger).With("k", "v")
for i := 1; i < 10; i++ { for i := 1; i < 10; i++ {
lc = log.With(lc, "k", "v") lc = lc.With("k", "v")
} }
b.ReportAllocs() b.ReportAllocs()
b.ResetTimer() b.ResetTimer()

View File

@ -12,7 +12,7 @@ func TestNopLogger(t *testing.T) {
if err := logger.Log("abc", 123); err != nil { if err := logger.Log("abc", 123); err != nil {
t.Error(err) t.Error(err)
} }
if err := log.With(logger, "def", "ghi").Log(); err != nil { if err := log.NewContext(logger).With("def", "ghi").Log(); err != nil {
t.Error(err) t.Error(err)
} }
} }

View File

@ -39,7 +39,7 @@ func TimestampKey(key string) StdlibAdapterOption {
return func(a *StdlibAdapter) { a.timestampKey = key } return func(a *StdlibAdapter) { a.timestampKey = key }
} }
// FileKey sets the key for the file and line field. By default, it's "caller". // FileKey sets the key for the file and line field. By default, it's "file".
func FileKey(key string) StdlibAdapterOption { func FileKey(key string) StdlibAdapterOption {
return func(a *StdlibAdapter) { a.fileKey = key } return func(a *StdlibAdapter) { a.fileKey = key }
} }
@ -55,7 +55,7 @@ func NewStdlibAdapter(logger Logger, options ...StdlibAdapterOption) io.Writer {
a := StdlibAdapter{ a := StdlibAdapter{
Logger: logger, Logger: logger,
timestampKey: "ts", timestampKey: "ts",
fileKey: "caller", fileKey: "file",
messageKey: "msg", messageKey: "msg",
} }
for _, option := range options { for _, option := range options {

View File

@ -35,9 +35,9 @@ func TestStdlibAdapterUsage(t *testing.T) {
log.Ldate: "ts=" + date + " msg=hello\n", log.Ldate: "ts=" + date + " msg=hello\n",
log.Ltime: "ts=" + time + " msg=hello\n", log.Ltime: "ts=" + time + " msg=hello\n",
log.Ldate | log.Ltime: "ts=\"" + date + " " + time + "\" msg=hello\n", log.Ldate | log.Ltime: "ts=\"" + date + " " + time + "\" msg=hello\n",
log.Lshortfile: "caller=stdlib_test.go:44 msg=hello\n", log.Lshortfile: "file=stdlib_test.go:44 msg=hello\n",
log.Lshortfile | log.Ldate: "ts=" + date + " caller=stdlib_test.go:44 msg=hello\n", log.Lshortfile | log.Ldate: "ts=" + date + " file=stdlib_test.go:44 msg=hello\n",
log.Lshortfile | log.Ldate | log.Ltime: "ts=\"" + date + " " + time + "\" caller=stdlib_test.go:44 msg=hello\n", log.Lshortfile | log.Ldate | log.Ltime: "ts=\"" + date + " " + time + "\" file=stdlib_test.go:44 msg=hello\n",
} { } {
buf.Reset() buf.Reset()
stdlog.SetFlags(flag) stdlog.SetFlags(flag)
@ -58,11 +58,11 @@ func TestStdLibAdapterExtraction(t *testing.T) {
"2009/01/23 01:23:23: hello": "ts=\"2009/01/23 01:23:23\" msg=hello\n", "2009/01/23 01:23:23: hello": "ts=\"2009/01/23 01:23:23\" msg=hello\n",
"01:23:23: hello": "ts=01:23:23 msg=hello\n", "01:23:23: hello": "ts=01:23:23 msg=hello\n",
"2009/01/23 01:23:23.123123: hello": "ts=\"2009/01/23 01:23:23.123123\" msg=hello\n", "2009/01/23 01:23:23.123123: hello": "ts=\"2009/01/23 01:23:23.123123\" msg=hello\n",
"2009/01/23 01:23:23.123123 /a/b/c/d.go:23: hello": "ts=\"2009/01/23 01:23:23.123123\" caller=/a/b/c/d.go:23 msg=hello\n", "2009/01/23 01:23:23.123123 /a/b/c/d.go:23: hello": "ts=\"2009/01/23 01:23:23.123123\" file=/a/b/c/d.go:23 msg=hello\n",
"01:23:23.123123 /a/b/c/d.go:23: hello": "ts=01:23:23.123123 caller=/a/b/c/d.go:23 msg=hello\n", "01:23:23.123123 /a/b/c/d.go:23: hello": "ts=01:23:23.123123 file=/a/b/c/d.go:23 msg=hello\n",
"2009/01/23 01:23:23 /a/b/c/d.go:23: hello": "ts=\"2009/01/23 01:23:23\" caller=/a/b/c/d.go:23 msg=hello\n", "2009/01/23 01:23:23 /a/b/c/d.go:23: hello": "ts=\"2009/01/23 01:23:23\" file=/a/b/c/d.go:23 msg=hello\n",
"2009/01/23 /a/b/c/d.go:23: hello": "ts=2009/01/23 caller=/a/b/c/d.go:23 msg=hello\n", "2009/01/23 /a/b/c/d.go:23: hello": "ts=2009/01/23 file=/a/b/c/d.go:23 msg=hello\n",
"/a/b/c/d.go:23: hello": "caller=/a/b/c/d.go:23 msg=hello\n", "/a/b/c/d.go:23: hello": "file=/a/b/c/d.go:23 msg=hello\n",
} { } {
buf.Reset() buf.Reset()
fmt.Fprint(writer, input) fmt.Fprint(writer, input)

View File

@ -42,7 +42,7 @@ const (
// https://en.wikipedia.org/wiki/ANSI_escape_code#Colors. // https://en.wikipedia.org/wiki/ANSI_escape_code#Colors.
var ( var (
resetColorBytes = []byte("\x1b[39;49;22m") resetColorBytes = []byte("\x1b[39;49m")
fgColorBytes [][]byte fgColorBytes [][]byte
bgColorBytes [][]byte bgColorBytes [][]byte
) )

View File

@ -27,7 +27,7 @@ func TestColorLogger(t *testing.T) {
if err := logger.Log("a", 1); err != nil { if err := logger.Log("a", 1); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if want, have := "\x1b[32;1m\x1b[47;1ma=1\n\x1b[39;49;22m", buf.String(); want != have { if want, have := "\x1b[32;1m\x1b[47;1ma=1\n\x1b[39;49m", buf.String(); want != have {
t.Errorf("\nwant %#v\nhave %#v", want, have) t.Errorf("\nwant %#v\nhave %#v", want, have)
} }
} }
@ -56,7 +56,7 @@ func TestColorLoggerConcurrency(t *testing.T) {
// copied from log/benchmark_test.go // copied from log/benchmark_test.go
func benchmarkRunner(b *testing.B, logger log.Logger, f func(log.Logger)) { func benchmarkRunner(b *testing.B, logger log.Logger, f func(log.Logger)) {
lc := log.With(logger, "common_key", "common_value") lc := log.NewContext(logger).With("common_key", "common_value")
b.ReportAllocs() b.ReportAllocs()
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
@ -66,7 +66,7 @@ func benchmarkRunner(b *testing.B, logger log.Logger, f func(log.Logger)) {
var ( var (
baseMessage = func(logger log.Logger) { logger.Log("foo_key", "foo_value") } baseMessage = func(logger log.Logger) { logger.Log("foo_key", "foo_value") }
withMessage = func(logger log.Logger) { log.With(logger, "a", "b").Log("c", "d") } withMessage = func(logger log.Logger) { log.NewContext(logger).With("a", "b").Log("c", "d") }
) )
// copied from log/concurrency_test.go // copied from log/concurrency_test.go

View File

@ -6,9 +6,9 @@ import (
"github.com/go-stack/stack" "github.com/go-stack/stack"
) )
// A Valuer generates a log value. When passed to With or WithPrefix in a // A Valuer generates a log value. When passed to Context.With in a value
// value element (odd indexes), it represents a dynamic value which is re- // element (odd indexes), it represents a dynamic value which is re-evaluated
// evaluated with each log event. // with each log event.
type Valuer func() interface{} type Valuer func() interface{}
// bindValues replaces all value elements (odd indexes) containing a Valuer // bindValues replaces all value elements (odd indexes) containing a Valuer
@ -39,6 +39,16 @@ func Timestamp(t func() time.Time) Valuer {
return func() interface{} { return t() } return func() interface{} { return t() }
} }
var (
// DefaultTimestamp is a Valuer that returns the current wallclock time,
// respecting time zones, when bound.
DefaultTimestamp Valuer = func() interface{} { return time.Now().Format(time.RFC3339) }
// DefaultTimestampUTC is a Valuer that returns the current time in UTC
// when bound.
DefaultTimestampUTC Valuer = func() interface{} { return time.Now().UTC().Format(time.RFC3339) }
)
// Caller returns a Valuer that returns a file and line from a specified depth // Caller returns a Valuer that returns a file and line from a specified depth
// in the callstack. Users will probably want to use DefaultCaller. // in the callstack. Users will probably want to use DefaultCaller.
func Caller(depth int) Valuer { func Caller(depth int) Valuer {
@ -46,18 +56,6 @@ func Caller(depth int) Valuer {
} }
var ( var (
// DefaultTimestamp is a Valuer that returns the current wallclock time,
// respecting time zones, when bound.
DefaultTimestamp = Valuer(func() interface{} {
return time.Now().Format(time.RFC3339Nano)
})
// DefaultTimestampUTC is a Valuer that returns the current time in UTC
// when bound.
DefaultTimestampUTC = Valuer(func() interface{} {
return time.Now().UTC().Format(time.RFC3339Nano)
})
// DefaultCaller is a Valuer that returns the file and line where the Log // DefaultCaller is a Valuer that returns the file and line where the Log
// method was invoked. It can only be used with log.With. // method was invoked. It can only be used with log.With.
DefaultCaller = Caller(3) DefaultCaller = Caller(3)

Some files were not shown because too many files have changed in this diff Show More