Merge pull request #62 from moul/dev/moul/improve-dependency-support

Support Dependency Lookup
This commit is contained in:
Manfred Touron 2017-05-19 09:37:35 +02:00 committed by GitHub
commit 30b0ec5000
656 changed files with 56388 additions and 33313 deletions

View File

@ -16,6 +16,7 @@ test: install
cd examples/flow && make
cd examples/sitemap && make
cd examples/go-generate && make
cd examples/single-package-mode && make
# cd examples/go-kit && make
.PHONY: docker.build

View File

@ -39,15 +39,21 @@ input.proto templates/doc.txt.tmpl templates/config.json.tmpl
doc.txt config.json
```
---
### Options
You can specify a custom `template_dir` or enable `debug`:
You can specify custom options, as follow:
```console
$> protoc --gotemplate_out=debug=true,template_dir=/path/to/template/directory:. input.proto
```
---
| Option | Default Value | Accepted Values | Description
|-----------------------|---------------|---------------------------|-----------------------
| `template_dir`       | `./template` | absolute or relative path | path to look for templates
| `destination_dir`     | `.`           | absolute or relative path | base path to write output
| `single-package-mode` | *false* | `true` or `false` | if *true*, `protoc` won't accept multiple packages to be compiled at once (*!= from `all`*), but will support `Message` lookup across the imported protobuf dependencies
| `debug`               | *false*       | `true` or `false` | if *true*, `protoc` will generate a more verbose output
| `all`                 | *false*       | `true` or `false`         | if *true*, protobuf files without `Service` will also be parsed
##### Hints
@ -61,13 +67,34 @@ See [examples](./examples).
This project uses [Masterminds/sprig](https://github.com/Masterminds/sprig) library and additional functions to extend the builtin [text/template](https://golang.org/pkg/text/template) helpers.
Non-exhaustive list of new helpers:
Non-exhaustive list of new helpers:s
* **all the functions from [sprig](https://github.com/Masterminds/sprig)**
* `json`
* `prettyjson`
* `first`
* `last`
* `splitArray`
* `upperFirst`
* `lowerFirst`
* `camelCase`
* `lowerCamelCase`
* `kebabCase`
* `snakeCase`
* `getProtoFile`
* `getMessageType`
* `getEnumValue`
* `isFieldMessage`
* `isFieldRepeated`
* `goType`
* `goTypeWithPackage`
* `jsType`
* `jsSuffixReserved`
* `namespacedFlowType`
* `httpVerb`
* `httpPath`
* `shortType`
* `urlHasVarsFromMessage`
See the project helpers for the complete list.

View File

@ -0,0 +1,26 @@
.PHONY: re
re: clean build test
.PHONY: build
build:
@mkdir -p output
@# proto-gen-go
protoc -I./proto --go_out=plugins=grpc:output proto/aaa/aaa.proto
protoc -I./proto --go_out=plugins=grpc:output proto/bbb/bbb.proto
@rm -rf output/aaa output/bbb
@mv output/github.com/moul/protoc-gen-gotemplate/examples/single-package-mode/output/* output/
@rm -rf output/github.com
@# protoc-gen-gotemplate
protoc -I./proto --gotemplate_out=template_dir=templates,single-package-mode=true:output proto/bbb/bbb.proto
gofmt -w .
.PHONY: test
test:
go test -i ./output/...
go test -v ./output/...
.PHONY: clean
clean:
rm -rf output

View File

@ -0,0 +1,84 @@
// Code generated by protoc-gen-go.
// source: aaa/aaa.proto
// DO NOT EDIT!
/*
Package aaa is a generated protocol buffer package.
It is generated from these files:
aaa/aaa.proto
It has these top-level messages:
AaaRequest
AaaReply
*/
package aaa
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
type AaaRequest struct {
Blah string `protobuf:"bytes,1,opt,name=blah" json:"blah,omitempty"`
}
func (m *AaaRequest) Reset() { *m = AaaRequest{} }
func (m *AaaRequest) String() string { return proto.CompactTextString(m) }
func (*AaaRequest) ProtoMessage() {}
func (*AaaRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
func (m *AaaRequest) GetBlah() string {
if m != nil {
return m.Blah
}
return ""
}
type AaaReply struct {
Error string `protobuf:"bytes,1,opt,name=error" json:"error,omitempty"`
}
func (m *AaaReply) Reset() { *m = AaaReply{} }
func (m *AaaReply) String() string { return proto.CompactTextString(m) }
func (*AaaReply) ProtoMessage() {}
func (*AaaReply) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }
func (m *AaaReply) GetError() string {
if m != nil {
return m.Error
}
return ""
}
func init() {
proto.RegisterType((*AaaRequest)(nil), "the.aaa.package.AaaRequest")
proto.RegisterType((*AaaReply)(nil), "the.aaa.package.AaaReply")
}
func init() { proto.RegisterFile("aaa/aaa.proto", fileDescriptor0) }
var fileDescriptor0 = []byte{
// 172 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x2c, 0x8e, 0x31, 0x0b, 0xc2, 0x30,
0x10, 0x46, 0x29, 0xa8, 0x68, 0x40, 0x84, 0xe0, 0xe0, 0x58, 0x3a, 0xb9, 0xa4, 0x19, 0xfc, 0x05,
0xba, 0x8b, 0xd0, 0xd1, 0xed, 0x5a, 0x8f, 0xb4, 0x78, 0xe9, 0xc5, 0xf4, 0x02, 0xfa, 0xef, 0xc5,
0xd8, 0xed, 0x1e, 0xbc, 0xe3, 0x7b, 0x6a, 0x0b, 0x00, 0x16, 0x00, 0xea, 0x10, 0x59, 0x58, 0xef,
0xa4, 0xc7, 0x3a, 0x23, 0x74, 0x4f, 0x70, 0x58, 0x95, 0x4a, 0x9d, 0x01, 0x1a, 0x7c, 0x25, 0x9c,
0x44, 0x6b, 0xb5, 0x68, 0x09, 0xfa, 0x43, 0x51, 0x16, 0xc7, 0x4d, 0x93, 0xef, 0xaa, 0x54, 0xeb,
0x6c, 0x04, 0xfa, 0xe8, 0xbd, 0x5a, 0x62, 0x8c, 0x1c, 0x67, 0xe1, 0x0f, 0x97, 0xdb, 0xfd, 0xea,
0x06, 0xe9, 0x53, 0x5b, 0x77, 0xec, 0xad, 0xe7, 0x44, 0x36, 0xaf, 0x75, 0xc6, 0xe1, 0x68, 0x1c,
0x0b, 0xfa, 0x40, 0x20, 0x68, 0xf1, 0x0d, 0x3e, 0x10, 0x4e, 0x76, 0x1a, 0x46, 0x47, 0x68, 0xe6,
0x08, 0xe3, 0xf9, 0x81, 0x96, 0x93, 0x84, 0x24, 0xbf, 0xd6, 0x76, 0x95, 0xdf, 0x4f, 0xdf, 0x00,
0x00, 0x00, 0xff, 0xff, 0x93, 0x24, 0x48, 0x9c, 0xbd, 0x00, 0x00, 0x00,
}

View File

@ -0,0 +1,200 @@
// Code generated by protoc-gen-go.
// source: bbb/bbb.proto
// DO NOT EDIT!
/*
Package bbb is a generated protocol buffer package.
It is generated from these files:
bbb/bbb.proto
It has these top-level messages:
BbbRequest
BbbReply
*/
package bbb
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
import the_aaa_package "github.com/moul/protoc-gen-gotemplate/examples/single-package-mode/output/aaa"
import (
context "golang.org/x/net/context"
grpc "google.golang.org/grpc"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
type BbbRequest struct {
Enable bool `protobuf:"varint,1,opt,name=enable" json:"enable,omitempty"`
}
func (m *BbbRequest) Reset() { *m = BbbRequest{} }
func (m *BbbRequest) String() string { return proto.CompactTextString(m) }
func (*BbbRequest) ProtoMessage() {}
func (*BbbRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
func (m *BbbRequest) GetEnable() bool {
if m != nil {
return m.Enable
}
return false
}
type BbbReply struct {
Done bool `protobuf:"varint,1,opt,name=done" json:"done,omitempty"`
}
func (m *BbbReply) Reset() { *m = BbbReply{} }
func (m *BbbReply) String() string { return proto.CompactTextString(m) }
func (*BbbReply) ProtoMessage() {}
func (*BbbReply) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }
func (m *BbbReply) GetDone() bool {
if m != nil {
return m.Done
}
return false
}
func init() {
proto.RegisterType((*BbbRequest)(nil), "the.bbb.package.BbbRequest")
proto.RegisterType((*BbbReply)(nil), "the.bbb.package.BbbReply")
}
// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConn
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
const _ = grpc.SupportPackageIsVersion4
// Client API for BbbService service
type BbbServiceClient interface {
Aaa(ctx context.Context, in *the_aaa_package.AaaRequest, opts ...grpc.CallOption) (*the_aaa_package.AaaReply, error)
Bbb(ctx context.Context, in *BbbRequest, opts ...grpc.CallOption) (*BbbReply, error)
}
type bbbServiceClient struct {
cc *grpc.ClientConn
}
func NewBbbServiceClient(cc *grpc.ClientConn) BbbServiceClient {
return &bbbServiceClient{cc}
}
func (c *bbbServiceClient) Aaa(ctx context.Context, in *the_aaa_package.AaaRequest, opts ...grpc.CallOption) (*the_aaa_package.AaaReply, error) {
out := new(the_aaa_package.AaaReply)
err := grpc.Invoke(ctx, "/the.bbb.package.BbbService/Aaa", in, out, c.cc, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *bbbServiceClient) Bbb(ctx context.Context, in *BbbRequest, opts ...grpc.CallOption) (*BbbReply, error) {
out := new(BbbReply)
err := grpc.Invoke(ctx, "/the.bbb.package.BbbService/Bbb", in, out, c.cc, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// Server API for BbbService service
type BbbServiceServer interface {
Aaa(context.Context, *the_aaa_package.AaaRequest) (*the_aaa_package.AaaReply, error)
Bbb(context.Context, *BbbRequest) (*BbbReply, error)
}
func RegisterBbbServiceServer(s *grpc.Server, srv BbbServiceServer) {
s.RegisterService(&_BbbService_serviceDesc, srv)
}
func _BbbService_Aaa_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(the_aaa_package.AaaRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(BbbServiceServer).Aaa(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/the.bbb.package.BbbService/Aaa",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(BbbServiceServer).Aaa(ctx, req.(*the_aaa_package.AaaRequest))
}
return interceptor(ctx, in, info, handler)
}
func _BbbService_Bbb_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(BbbRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(BbbServiceServer).Bbb(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/the.bbb.package.BbbService/Bbb",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(BbbServiceServer).Bbb(ctx, req.(*BbbRequest))
}
return interceptor(ctx, in, info, handler)
}
var _BbbService_serviceDesc = grpc.ServiceDesc{
ServiceName: "the.bbb.package.BbbService",
HandlerType: (*BbbServiceServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "Aaa",
Handler: _BbbService_Aaa_Handler,
},
{
MethodName: "Bbb",
Handler: _BbbService_Bbb_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "bbb/bbb.proto",
}
func init() { proto.RegisterFile("bbb/bbb.proto", fileDescriptor0) }
var fileDescriptor0 = []byte{
// 242 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x90, 0x41, 0x4b, 0x03, 0x31,
0x10, 0x85, 0x59, 0x2a, 0xa5, 0x04, 0x8a, 0x90, 0x83, 0x68, 0x05, 0x91, 0xe2, 0xc1, 0xcb, 0x26,
0xa0, 0xe7, 0x1e, 0xba, 0x77, 0x11, 0xea, 0xcd, 0xdb, 0xcc, 0x76, 0x48, 0x17, 0x93, 0x9d, 0xd8,
0x9d, 0x88, 0xfd, 0x0b, 0xfe, 0x6a, 0xd9, 0x34, 0xa2, 0x60, 0x6f, 0x19, 0xde, 0xbc, 0xf7, 0xbd,
0x8c, 0x9a, 0x23, 0xa2, 0x45, 0x44, 0x13, 0xf7, 0x2c, 0xac, 0xcf, 0x65, 0x47, 0x26, 0x8f, 0xd0,
0xbe, 0x81, 0xa3, 0xc5, 0x1c, 0x00, 0x2c, 0x00, 0x1c, 0xf5, 0xe5, 0x9d, 0x52, 0x0d, 0xe2, 0x86,
0xde, 0x13, 0x0d, 0xa2, 0x2f, 0xd4, 0x94, 0x7a, 0x40, 0x4f, 0x97, 0xd5, 0x6d, 0x75, 0x3f, 0xdb,
0x94, 0x69, 0x79, 0xa3, 0x66, 0x79, 0x2b, 0xfa, 0x83, 0xd6, 0xea, 0x6c, 0xcb, 0xfd, 0xcf, 0x46,
0x7e, 0x3f, 0x7c, 0x55, 0x39, 0xe6, 0x85, 0xf6, 0x1f, 0x5d, 0x4b, 0x7a, 0xa5, 0x26, 0x6b, 0x00,
0x7d, 0x6d, 0x46, 0x78, 0x66, 0x1d, 0xe1, 0x66, 0x0d, 0x50, 0x50, 0x8b, 0xab, 0xd3, 0xe2, 0x48,
0x58, 0xa9, 0x49, 0x83, 0x58, 0xec, 0x7f, 0xba, 0x9b, 0xdf, 0xa6, 0xc5, 0xfe, 0x4f, 0x8c, 0xfe,
0xd0, 0x3c, 0xbf, 0x3e, 0xb9, 0x4e, 0x76, 0x09, 0x4d, 0xcb, 0xc1, 0x06, 0x4e, 0xde, 0xe6, 0xbf,
0xb6, 0xb5, 0xa3, 0xbe, 0x76, 0x2c, 0x14, 0xa2, 0x07, 0x21, 0x4b, 0x9f, 0x10, 0xa2, 0xa7, 0xc1,
0x0e, 0x5d, 0xef, 0x3c, 0xd5, 0x25, 0xa9, 0x0e, 0xbc, 0x25, 0xcb, 0x49, 0x62, 0x92, 0xf1, 0x92,
0x38, 0xcd, 0xf6, 0xc7, 0xef, 0x00, 0x00, 0x00, 0xff, 0xff, 0xe0, 0xe1, 0x26, 0xcb, 0x5b, 0x01,
0x00, 0x00,
}

View File

@ -0,0 +1,19 @@
// file generated with protoc-gen-gotemplate
package bbb
import (
"fmt"
"github.com/moul/protoc-gen-gotemplate/examples/single-package-mode/output/aaa"
"golang.org/x/net/context"
)
type Service struct{}
func (service Service) Aaa(ctx context.Context, input *aaa.AaaRequest) (*aaa.AaaReply, error) {
return nil, fmt.Errorf("method not implemented")
}
func (service Service) Bbb(ctx context.Context, input *BbbRequest) (*BbbReply, error) {
return nil, fmt.Errorf("method not implemented")
}

View File

@ -0,0 +1,12 @@
syntax = "proto3";
option go_package = "github.com/moul/protoc-gen-gotemplate/examples/single-package-mode/output/aaa";
package the.aaa.package;
message AaaRequest {
string blah = 1;
}
message AaaReply {
string error = 1;
}

View File

@ -0,0 +1,19 @@
syntax = "proto3";
package the.bbb.package;
option go_package = "github.com/moul/protoc-gen-gotemplate/examples/single-package-mode/output/bbb";
import "aaa/aaa.proto";
service BbbService {
rpc Aaa(the.aaa.package.AaaRequest) returns (the.aaa.package.AaaReply);
rpc Bbb(BbbRequest) returns (BbbReply);
}
message BbbRequest {
bool enable = 1;
}
message BbbReply {
bool done = 1;
}

View File

@ -0,0 +1,26 @@
// file generated with protoc-gen-gotemplate
package {{.File.Name | dir}}
{{- $file := .File}}
{{- $currentFile := $file.Name | getProtoFile}}
import (
"fmt"
"golang.org/x/net/context"
{{- range .File.Dependency}}
{{- $dependency := . | getProtoFile}}
{{$dependency.GoPkg}}
{{end}}
)
type Service struct {}
{{- range .Service.Method}}
{{- $in := .InputType | getMessageType $file}}
{{- $out := .OutputType | getMessageType $file}}
func (service Service) {{.Name}}(ctx context.Context, input *{{$in.GoType $currentFile.GoPkg.Path}}) (*{{$out.GoType $currentFile.GoPkg.Path}}, error) {
return nil, fmt.Errorf("method not implemented")
}
{{end}}

41
glide.lock generated
View File

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

View File

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

View File

@ -8,10 +8,10 @@ import (
"text/template"
"github.com/Masterminds/sprig"
"github.com/huandu/xstrings"
"github.com/golang/protobuf/proto"
"github.com/golang/protobuf/protoc-gen-go/descriptor"
ggdescriptor "github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway/descriptor"
"github.com/huandu/xstrings"
options "google.golang.org/genproto/googleapis/api/annotations"
)
@ -64,6 +64,7 @@ var ProtoHelpersFuncMap = template.FuncMap{
return strings.Replace(xstrings.ToSnakeCase(s), "_", "-", -1)
},
"snakeCase": xstrings.ToSnakeCase,
"getProtoFile": getProtoFile,
"getMessageType": getMessageType,
"getEnumValue": getEnumValue,
"isFieldMessage": isFieldMessage,
@ -85,17 +86,37 @@ func init() {
}
}
func getMessageType(f *descriptor.FileDescriptorProto, name string) *descriptor.DescriptorProto {
func getProtoFile(name string) *ggdescriptor.File {
if registry == nil {
return nil
}
file, err := registry.LookupFile(name)
if err != nil {
panic(err)
}
return file
}
func getMessageType(f *descriptor.FileDescriptorProto, name string) *ggdescriptor.Message {
if registry != nil {
msg, err := registry.LookupMsg(".", name)
if err != nil {
panic(err)
}
return msg
}
// name is in the form .packageName.MessageTypeName.InnerMessageTypeName...
// e.g. .article.ProductTag
splits := strings.Split(name, ".")
target := splits[len(splits)-1]
for _, m := range f.MessageType {
if target == *m.Name {
return m
return &ggdescriptor.Message{
DescriptorProto: m,
}
}
}
return nil
}

43
main.go
View File

@ -9,6 +9,11 @@ import (
"github.com/golang/protobuf/proto"
"github.com/golang/protobuf/protoc-gen-go/generator"
"github.com/golang/protobuf/protoc-gen-go/plugin"
ggdescriptor "github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway/descriptor"
)
var (
registry *ggdescriptor.Registry // some helpers need access to registry
)
func main() {
@ -30,10 +35,13 @@ func main() {
g.CommandLineParameters(g.Request.GetParameter())
// Parse parameters
templateDir := "./templates"
destinationDir := "."
debug := false
all := false
var (
templateDir = "./templates"
destinationDir = "."
debug = false
all = false
singlePackageMode = false
)
if parameter := g.Request.GetParameter(); parameter != "" {
for _, param := range strings.Split(parameter, ",") {
parts := strings.Split(param, "=")
@ -48,6 +56,15 @@ func main() {
case "destination_dir":
destinationDir = parts[1]
break
case "single-package-mode":
switch strings.ToLower(parts[1]) {
case "true", "t":
singlePackageMode = true
case "false", "f":
default:
log.Printf("Err: invalid value for single-package-mode: %q", parts[1])
}
break
case "debug":
switch strings.ToLower(parts[1]) {
case "true", "t":
@ -74,17 +91,29 @@ func main() {
tmplMap := make(map[string]*plugin_go.CodeGeneratorResponse_File)
concatOrAppend := func(file *plugin_go.CodeGeneratorResponse_File) {
if val, ok := tmplMap[*file.Name]; ok {
*val.Content += *file.Content
if val, ok := tmplMap[file.GetName()]; ok {
*val.Content += file.GetContent()
} else {
tmplMap[*file.Name] = file
tmplMap[file.GetName()] = file
g.Response.File = append(g.Response.File, file)
}
}
if singlePackageMode {
registry = ggdescriptor.NewRegistry()
if err := registry.Load(g.Request); err != nil {
g.Error(err, "registry: failed to load the request")
}
}
// Generate the encoders
for _, file := range g.Request.GetProtoFile() {
if all {
if singlePackageMode {
if _, err := registry.LookupFile(file.GetName()); err != nil {
g.Error(err, "registry: failed to lookup file %q", file.GetName())
}
}
encoder := NewGenericTemplateBasedEncoder(templateDir, file, debug, destinationDir)
for _, tmpl := range encoder.Files() {
concatOrAppend(tmpl)

View File

@ -1,4 +1,6 @@
# 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
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.
It is inspired by the template functions found in
[Twig](http://twig.sensiolabs.org/documentation).
[![Build Status](https://travis-ci.org/Masterminds/sprig.svg?branch=master)](https://travis-ci.org/Masterminds/sprig)
[Twig](http://twig.sensiolabs.org/documentation) and also in various
JavaScript libraries, such as [underscore.js](http://underscorejs.org/).
## 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
read on for standard usage.
@ -54,241 +59,6 @@ Produces:
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:
The following principles were used in deciding on which functions to add, and

View File

@ -1,6 +1,7 @@
package sprig
import (
"encoding/json"
"reflect"
)
@ -60,3 +61,15 @@ func coalesce(v ...interface{}) interface{} {
}
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)
}
}
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
import "github.com/imdario/mergo"
func set(d map[string]interface{}, key string, value interface{}) map[string]interface{} {
d[key] = value
return d
@ -72,3 +74,11 @@ func dict(v ...interface{}) map[string]interface{} {
}
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))
}
}
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

@ -75,6 +75,15 @@ inserted.
A common idiom in Sprig templates is to uses `pluck... | first` to get the first
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

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.
- [String Functions](strings.html): `trim`, `wrap`, `randAlpha`, `plural`, etc.
- [String List Functions](string_slice.html): `splitList`, `sortAlpha`, etc.
- [Math Functions](math.html): `add`, `max`, `mul`, etc.
- [Integer Slice Functions](integer_slice.html): `until`, `untilStep`
- [Date Functions](date.html): `now`, `date`, etc.
- [Defaults Functions](defaults.html): `default`, `empty`, `coalesce`
- [Encoding Functions](encoding.html): `b64enc`, `b64dec`, etc.
- [Lists and List Functions](lists.html): `list`, `first`, `uniq`, etc.
- [Dictionaries and Dict Functions](dicts.html): `dict`, `hasKey`, `pluck`, etc.
- [Type Conversion Functions](conversion.html): `atoi`, `int64`, `toString`, etc.
- [File Path Functions](paths.html): `base`, `dir`, `ext`, `clean`, `isAbs`
- [String Functions](strings.md): `trim`, `wrap`, `randAlpha`, `plural`, etc.
- [String List Functions](string_slice.md): `splitList`, `sortAlpha`, etc.
- [Math Functions](math.md): `add`, `max`, `mul`, etc.
- [Integer Slice Functions](integer_slice.md): `until`, `untilStep`
- [Date Functions](date.md): `now`, `date`, etc.
- [Defaults Functions](defaults.md): `default`, `empty`, `coalesce`
- [Encoding Functions](encoding.md): `b64enc`, `b64dec`, etc.
- [Lists and List Functions](lists.md): `list`, `first`, `uniq`, etc.
- [Dictionaries and Dict Functions](dicts.md): `dict`, `hasKey`, `pluck`, etc.
- [Type Conversion Functions](conversion.md): `atoi`, `int64`, `toString`, etc.
- [File Path Functions](paths.md): `base`, `dir`, `ext`, `clean`, `isAbs`
- [Flow Control Functions](flow_control.md): `fail`
- Advanced Functions
- [UUID Functions](uuid.html): `uuidv4`
- [OS Functions](os.html): `env`, `expandenv`
- [Version Comparison Functions](semver.html): `semver`, `semverCompare`
- [Reflection](reflection.html): `typeOf`, `kindIs`, `typeIsLike`, etc.
- [Cryptographic and Security Functions](crypto.html): `derivePassword`, `sha256sum`, `genPrivateKey`
- [UUID Functions](uuid.md): `uuidv4`
- [OS Functions](os.md): `env`, `expandenv`
- [Version Comparison Functions](semver.md): `semver`, `semverCompare`
- [Reflection](reflection.md): `typeOf`, `kindIs`, `typeIsLike`, etc.
- [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
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...
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
import (
"errors"
"html/template"
"os"
"path"
@ -10,6 +11,7 @@ import (
"time"
util "github.com/aokoli/goutils"
"github.com/huandu/xstrings"
)
// Produce the function map.
@ -122,6 +124,9 @@ var genericMap = map[string]interface{}{
"randAscii": randAscii,
"randNumeric": randNumeric,
"swapcase": util.SwapCase,
"shuffle": xstrings.Shuffle,
"snakecase": xstrings.ToSnakeCase,
"camelcase": xstrings.ToCamelCase,
"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) },
// Switch order so that "foobar" | contains "foo"
@ -189,6 +194,8 @@ var genericMap = map[string]interface{}{
"empty": empty,
"coalesce": coalesce,
"compact": compact,
"toJson": toJson,
"toPrettyJson": toPrettyJson,
// Reflection
"typeOf": typeOf,
@ -225,6 +232,7 @@ var genericMap = map[string]interface{}{
"keys": keys,
"pick": pick,
"omit": omit,
"merge": merge,
"append": push, "push": push,
"prepend": prepend,
@ -247,4 +255,7 @@ var genericMap = map[string]interface{}{
// SemVer:
"semver": semver,
"semverCompare": semverCompare,
// Flow Control:
"fail": func(msg string) (string, error) { return "", errors.New(msg) },
}

View File

@ -3,10 +3,12 @@ package sprig
import (
"bytes"
"fmt"
"math/rand"
"os"
"testing"
"text/template"
"github.com/aokoli/goutils"
"github.com/stretchr/testify/assert"
)
@ -47,6 +49,30 @@ func TestExt(t *testing.T) {
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.
func runt(tpl, expect string) error {
return runtv(tpl, expect, map[string]string{})

View File

@ -1,14 +1,24 @@
hash: c2d7cb87ff32a0aba767b90bff630c3e4c1ca9904fc72568414d20d79a41e70f
updated: 2017-03-13T18:38:30.597881175-06:00
hash: b9cc40bfd6dde74a94103b96700df1a9ab29a7fff5650216cf5a05f4fe72fb73
updated: 2017-05-02T16:01:04.617727646-06:00
imports:
- name: github.com/aokoli/goutils
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
version: 59c29afe1a994eacb71c833025ca7acf874bb1da
- name: github.com/satori/go.uuid
version: 879c5887cd475cd7864858769793b2ceb0d44feb
- name: github.com/stretchr/testify
version: e3a8ff8ce36581f87a15341206f205b1da467059
subpackages:
- assert
- name: golang.org/x/crypto
version: 1f22c0103821b9390939b6776727195525381532
version: d172538b2cfce0c13cee31e647d0367aa8cd2486
subpackages:
- pbkdf2
- scrypt
@ -21,7 +31,3 @@ testImports:
version: d8ed2627bdf02c080bf22230dbb337003b7aba2d
subpackages:
- difflib
- name: github.com/stretchr/testify
version: e3a8ff8ce36581f87a15341206f205b1da467059
subpackages:
- assert

View File

@ -1,6 +1,7 @@
package: github.com/Masterminds/sprig
import:
- package: github.com/aokoli/goutils
- package: github.com/Masterminds/goutils
version: ^1.0.0
- package: github.com/satori/go.uuid
version: ^1.1.0
- package: golang.org/x/crypto
@ -8,3 +9,7 @@ import:
- scrypt
- package: github.com/Masterminds/semver
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
===========
[![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
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
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.
## 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
@ -26,7 +29,7 @@ The code snippets below show examples of how to use GoUtils. Some functions retu
import (
"fmt"
"github.com/aokoli/goutils"
"github.com/Masterminds/goutils"
)
func main() {
@ -41,7 +44,7 @@ Some functions return errors mainly due to illegal arguements used as parameters
import (
"fmt"
"github.com/aokoli/goutils"
"github.com/Masterminds/goutils"
)
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.
## 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
* [GoUtils webpage](http://aokoli.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.
* [GoUtils webpage](http://Masterminds.github.io/goutils/)

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
import (
"fmt"
"unicode"
"math"
"math/rand"
"time"
"unicode"
)
// Provides the time-based seed used to generate random #s
// RANDOM provides the time-based seed used to generate random numbers
var RANDOM = rand.New(rand.NewSource(time.Now().UnixNano()))
/*
@ -44,7 +42,6 @@ func RandomNonAlphaNumeric (count int) (string, error) {
return RandomAlphaNumericCustom(count, false, false)
}
/*
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).
@ -60,7 +57,6 @@ func RandomAscii(count int) (string, error) {
return Random(count, 32, 127, false, false)
}
/*
RandomNumeric creates a random string whose length is the number of characters specified.
Characters will be chosen from the set of numeric characters.
@ -76,7 +72,6 @@ func RandomNumeric (count int) (string, error) {
return Random(count, 0, 0, false, true)
}
/*
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.
@ -94,7 +89,6 @@ func RandomAlphabetic (count int) (string, error) {
return Random(count, 0, 0, true, false)
}
/*
RandomAlphaNumeric creates a random string whose length is the number of characters specified.
Characters will be chosen from the set of alpha-numeric characters.
@ -127,7 +121,6 @@ func RandomAlphaNumericCustom (count int, letters bool, numbers bool) (string, e
return Random(count, 0, 0, letters, numbers)
}
/*
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
@ -149,7 +142,6 @@ func Random (count int, start int, end int, letters bool, numbers bool, chars ..
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.
If the parameters start and end are both 0, start and end are set to ' ' and 'z', the ASCII printable characters, will be used,
@ -210,7 +202,6 @@ func RandomSeed (count int, start int, end int, letters bool, numbers bool, char
buffer := make([]rune, count)
gap := end - start
// high-surrogates range, (\uD800-\uDBFF) = 55296 - 56319
// low-surrogates range, (\uDC00-\uDFFF) = 56320 - 57343

View File

@ -1,27 +1,15 @@
package goutils
import (
"testing"
"fmt"
"math/rand"
"testing"
)
// ****************************** TESTS ********************************************
func TestRandomSeed(t *testing.T) {
// count, start, end, letters, numbers := 5, 0, 0, true, true
random := rand.New(rand.NewSource(10))
out := "3ip9v"
@ -38,7 +26,6 @@ func TestRandomSeed(t *testing.T) {
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)
out = "88935"
@ -46,7 +33,6 @@ func TestRandomSeed(t *testing.T) {
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)
out = "H_I;E"
@ -54,7 +40,6 @@ func TestRandomSeed(t *testing.T) {
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
chars := []rune{'1', '2', '3', 'a', 'b', 'c'}
out = "2b2ca"
@ -65,10 +50,8 @@ func TestRandomSeed(t *testing.T) {
}
// ****************************** EXAMPLES ********************************************
func ExampleRandomSeed() {
var seed int64 = 10 // If you change this seed #, the random sequence below will change

View File

@ -16,18 +16,16 @@ limitations under the License.
package goutils
import (
"fmt"
"unicode"
"bytes"
"fmt"
"strings"
"unicode"
)
// Typically returned by functions where a searched item cannot be found
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..."
@ -50,7 +48,6 @@ func Abbreviate (str string, maxWidth int) (string, error) {
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..."
This function works like Abbreviate(string, int), but allows you to specify a "left edge" offset. Note that this left edge is not
@ -99,7 +96,6 @@ func AbbreviateFull (str string, offset int, maxWidth int) (string, error) {
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).
It returns the string without whitespaces.
@ -130,7 +126,6 @@ func DeleteWhiteSpace(str string) string {
return chs.String()
}
/*
IndexOfDifference compares two strings, and returns the index at which the strings begin to differ.
@ -148,7 +143,7 @@ func IndexOfDifference(str1 string, str2 string) int {
if IsEmpty(str1) || IsEmpty(str2) {
return 0
}
var i int;
var i int
for i = 0; i < len(str1) && i < len(str2); i++ {
if rune(str1[i]) != rune(str2[i]) {
break
@ -160,8 +155,6 @@ func IndexOfDifference(str1 string, str2 string) int {
return INDEX_NOT_FOUND
}
/*
IsBlank checks if a string is whitespace or empty (""). Observe the following behavior:
@ -189,7 +182,6 @@ func IsBlank(str string) bool {
return true
}
/*
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.
@ -207,7 +199,7 @@ Returns:
*/
func IndexOf(str string, sub string, start int) int {
if (start < 0) {
if start < 0 {
start = 0
}

View File

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

View File

@ -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.
To convert the rest of each word to lowercase at the same time, use CapitalizeFully(str string, delimiters ...rune).
@ -174,7 +173,7 @@ func Capitalize (str string, delimiters ...rune) string {
}
if str == "" || delimLen == 0 {
return str;
return str
}
buffer := []rune(str)
@ -192,8 +191,6 @@ func Capitalize (str string, delimiters ...rune) string {
}
/*
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
@ -217,15 +214,13 @@ func CapitalizeFully (str string, delimiters ...rune) string {
delimLen = len(delimiters)
}
if str == "" || delimLen == 0 {
return str;
return 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.
The delimiters represent a set of characters understood to separate words. The first string character and the first non-delimiter
@ -249,7 +244,7 @@ func Uncapitalize (str string, delimiters ...rune) string {
}
if str == "" || delimLen == 0 {
return str;
return str
}
buffer := []rune(str)
@ -266,7 +261,6 @@ func Uncapitalize (str string, delimiters ...rune) string {
return string(buffer)
}
/*
SwapCase swaps the case of a string using a word based algorithm.
@ -311,10 +305,9 @@ func SwapCase(str string) string {
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
letters after the defined delimiters are returned as a new string. Their case is not changed. If the delimiters
@ -349,8 +342,6 @@ func Initials(str string, delimiters ...rune) string {
return buf.String()
}
// private function (lower case func name)
func isDelimiter(ch rune, delimiters ...rune) bool {
if delimiters == nil {

View File

@ -1,8 +1,8 @@
package goutils
import (
"testing"
"fmt"
"testing"
)
// ****************************** TESTS ********************************************
@ -18,7 +18,6 @@ func TestWrapNormalWord(t *testing.T) {
}
}
func TestWrapCustomLongWordFalse(t *testing.T) {
in := "BobManuelBob Bob"
@ -32,7 +31,6 @@ func TestWrapCustomLongWordFalse(t *testing.T) {
}
}
func TestWrapCustomLongWordTrue(t *testing.T) {
in := "BobManuelBob Bob"
@ -46,10 +44,8 @@ func TestWrapCustomLongWordTrue(t *testing.T) {
}
}
func TestCapitalize(t *testing.T) {
// Test 1: Checks if function works with 1 parameter, and default whitespace delimiter
in := "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)
}
// Test 2: Checks if function works with both parameters, with param 2 containing whitespace and '.'
out = "Test Is Going.Well.Thank.You.For Inquiring"
delimiters := []rune{' ', '.'}
@ -68,8 +63,6 @@ func TestCapitalize(t *testing.T) {
}
}
func TestCapitalizeFully(t *testing.T) {
// Test 1
@ -80,7 +73,6 @@ func TestCapitalizeFully(t *testing.T) {
t.Errorf("CapitalizeFully(%v) = %v, want %v", in, x, out)
}
// Test 2
out = "Test Is Going.Well.Thank.You.For Inquiring"
delimiters := []rune{' ', '.'}
@ -90,7 +82,6 @@ func TestCapitalizeFully(t *testing.T) {
}
}
func TestUncapitalize(t *testing.T) {
// 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) {
in := "This Is A.Test"
@ -121,7 +111,6 @@ func TestSwapCase(t *testing.T) {
}
}
func TestInitials(t *testing.T) {
// Test 1
@ -132,7 +121,6 @@ func TestInitials(t *testing.T) {
t.Errorf("Initials(%v) = %v, want %v", in, x, out)
}
// Test 2
out = "JDR"
delimiters := []rune{' ', '.'}
@ -143,10 +131,6 @@ func TestInitials(t *testing.T) {
}
// ****************************** EXAMPLES ********************************************
func ExampleWrap() {
@ -160,7 +144,6 @@ func ExampleWrap() {
// Bob Manuel
}
func ExampleWrapCustom_1() {
in := "BobManuelBob Bob"
@ -173,7 +156,6 @@ func ExampleWrapCustom_1() {
// BobManuelBob<br\>Bob
}
func ExampleWrapCustom_2() {
in := "BobManuelBob Bob"
@ -186,8 +168,6 @@ func ExampleWrapCustom_2() {
// BobManuelB<br\>ob Bob
}
func ExampleCapitalize() {
in := "test is going.well.thank.you.for inquiring" // Compare input to CapitalizeFully example
@ -200,11 +180,8 @@ func ExampleCapitalize() {
// Test Is Going.Well.Thank.You.For Inquiring
}
func ExampleCapitalizeFully() {
in := "tEsT iS goiNG.wELL.tHaNk.yOU.for inqUIrING" // Notice scattered capitalization
delimiters := []rune{' ', '.'}
@ -215,7 +192,6 @@ func ExampleCapitalizeFully() {
// Test Is Going.Well.Thank.You.For Inquiring
}
func ExampleUncapitalize() {
in := "This Is A.Test"
@ -228,7 +204,6 @@ func ExampleUncapitalize() {
// this is a.test
}
func ExampleSwapCase() {
in := "This Is A.Test"
@ -237,7 +212,6 @@ func ExampleSwapCase() {
// tHIS iS a.tEST
}
func ExampleInitials() {
in := "John Doe.Ray"
@ -249,4 +223,3 @@ func ExampleInitials() {
// JD
// 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:
* [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 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)
* [Directory of Examples](https://godoc.org/github.com/dgrijalva/jwt-go#pkg-examples)
## Extensions

View File

@ -10,6 +10,7 @@ import (
type Parser struct {
ValidMethods []string // If populated, only these methods will be considered valid
UseJSONNumber bool // Use JSON Number format in JSON decoder
SkipClaimsValidation bool // Skip claims validation during token parsing
}
// Parse, validate, and return a token.
@ -101,6 +102,7 @@ func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyf
vErr := &ValidationError{}
// Validate Claims
if !p.SkipClaimsValidation {
if err := token.Claims.Valid(); err != nil {
// If the Claims Valid returned an error, check if it is a validation error,
@ -111,6 +113,7 @@ func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyf
vErr = e
}
}
}
// Perform validation
token.Signature = parts[2]

View File

@ -172,6 +172,15 @@ var jwtTestData = []struct {
jwt.ValidationErrorNotValidYet | jwt.ValidationErrorExpired,
&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) {

View File

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

View File

@ -3,6 +3,7 @@ language: go
script: go test -race -v ./...
go:
- 1.7.5
- 1.8
- 1.5.4
- 1.6.3
- 1.7.1
- 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
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)
- [Martini](https://github.com/go-martini/martini)
- [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
- [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
- [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
import (
"context"
"golang.org/x/net/context"
"github.com/go-kit/kit/auth/jwt"
"github.com/go-kit/kit/log"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,6 @@
package circuitbreaker_test
import (
"context"
"errors"
"fmt"
"path/filepath"
@ -9,6 +8,8 @@ import (
"testing"
"time"
"golang.org/x/net/context"
"github.com/go-kit/kit/endpoint"
)

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
import (
"context"
"golang.org/x/net/context"
)
// Endpoint is the fundamental building block of servers and clients.

View File

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

View File

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

View File

@ -1,7 +1,6 @@
package main
import (
"context"
"flag"
"fmt"
"os"
@ -13,6 +12,7 @@ import (
"github.com/lightstep/lightstep-tracer-go"
stdopentracing "github.com/opentracing/opentracing-go"
zipkin "github.com/openzipkin/zipkin-go-opentracing"
"golang.org/x/net/context"
"google.golang.org/grpc"
"sourcegraph.com/sourcegraph/appdash"
appdashot "sourcegraph.com/sourcegraph/appdash/opentracing"
@ -40,8 +40,7 @@ func main() {
thriftProtocol = flag.String("thrift.protocol", "binary", "binary, compact, json, simplejson")
thriftBufferSize = flag.Int("thrift.buffer.size", 0, "0 for unbuffered")
thriftFramed = flag.Bool("thrift.framed", false, "true to enable framing")
zipkinAddr = flag.String("zipkin.addr", "", "Enable Zipkin tracing via a Zipkin HTTP Collector endpoint")
zipkinKafkaAddr = flag.String("zipkin.kafka.addr", "", "Enable Zipkin tracing via a Kafka server host:port")
zipkinAddr = flag.String("zipkin.addr", "", "Enable Zipkin tracing via a Kafka Collector 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")
method = flag.String("method", "sum", "sum, concat")
@ -58,34 +57,16 @@ func main() {
var tracer stdopentracing.Tracer
{
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(
strings.Split(*zipkinKafkaAddr, ","),
strings.Split(*zipkinAddr, ","),
zipkin.KafkaLogger(log.NewNopLogger()),
)
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"),
zipkin.NewRecorder(collector, false, "localhost:8000", "addcli"),
)
if err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)

View File

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

View File

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

View File

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

View File

@ -4,10 +4,8 @@ package addsvc
// It utilizes the transport/grpc.Server.
import (
"context"
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/log"
@ -16,18 +14,20 @@ import (
)
// 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{
grpctransport.ServerErrorLogger(logger),
}
return &grpcServer{
sum: grpctransport.NewServer(
ctx,
endpoints.SumEndpoint,
DecodeGRPCSumRequest,
EncodeGRPCSumResponse,
append(options, grpctransport.ServerBefore(opentracing.FromGRPCRequest(tracer, "Sum", logger)))...,
),
concat: grpctransport.NewServer(
ctx,
endpoints.ConcatEndpoint,
DecodeGRPCConcatRequest,
EncodeGRPCConcatResponse,
@ -41,7 +41,7 @@ type grpcServer struct {
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)
if err != nil {
return nil, err
@ -49,7 +49,7 @@ func (s *grpcServer) Sum(ctx oldcontext.Context, req *pb.SumRequest) (*pb.SumRep
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)
if err != nil {
return nil, err

View File

@ -5,13 +5,13 @@ package addsvc
import (
"bytes"
"context"
"encoding/json"
"errors"
"io/ioutil"
"net/http"
stdopentracing "github.com/opentracing/opentracing-go"
"golang.org/x/net/context"
"github.com/go-kit/kit/log"
"github.com/go-kit/kit/tracing/opentracing"
@ -20,19 +20,21 @@ import (
// MakeHTTPHandler returns a handler that makes a set of endpoints available
// 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{
httptransport.ServerErrorEncoder(errorEncoder),
httptransport.ServerErrorLogger(logger),
}
m := http.NewServeMux()
m.Handle("/sum", httptransport.NewServer(
ctx,
endpoints.SumEndpoint,
DecodeHTTPSumRequest,
EncodeHTTPGenericResponse,
append(options, httptransport.ServerBefore(opentracing.FromHTTPRequest(tracer, "Sum", logger)))...,
))
m.Handle("/concat", httptransport.NewServer(
ctx,
endpoints.ConcatEndpoint,
DecodeHTTPConcatRequest,
EncodeHTTPGenericResponse,
@ -45,10 +47,19 @@ func errorEncoder(_ context.Context, err error, w http.ResponseWriter) {
code := http.StatusInternalServerError
msg := err.Error()
switch err {
if e, ok := err.(httptransport.Error); ok {
msg = e.Err.Error()
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)
json.NewEncoder(w).Encode(errorWrapper{Error: msg})

View File

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

View File

@ -2,7 +2,6 @@ package main
import (
"bytes"
"context"
"encoding/json"
"flag"
"fmt"
@ -19,6 +18,7 @@ import (
"github.com/gorilla/mux"
"github.com/hashicorp/consul/api"
stdopentracing "github.com/opentracing/opentracing-go"
"golang.org/x/net/context"
"github.com/go-kit/kit/endpoint"
"github.com/go-kit/kit/examples/addsvc"
@ -44,8 +44,8 @@ func main() {
var logger log.Logger
{
logger = log.NewLogfmtLogger(os.Stderr)
logger = log.With(logger, "ts", log.DefaultTimestampUTC)
logger = log.With(logger, "caller", log.DefaultCaller)
logger = log.NewContext(logger).With("ts", log.DefaultTimestampUTC)
logger = log.NewContext(logger).With("caller", log.DefaultCaller)
}
// 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
// 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.
@ -140,8 +140,8 @@ func main() {
// have to do provide it with the encode and decode functions for our
// stringsvc methods.
r.Handle("/stringsvc/uppercase", httptransport.NewServer(uppercase, decodeUppercaseRequest, encodeJSONResponse))
r.Handle("/stringsvc/count", httptransport.NewServer(count, decodeCountRequest, encodeJSONResponse))
r.Handle("/stringsvc/uppercase", httptransport.NewServer(ctx, uppercase, decodeUppercaseRequest, encodeJSONResponse))
r.Handle("/stringsvc/count", httptransport.NewServer(ctx, count, decodeCountRequest, encodeJSONResponse))
}
// Interrupt handler.

View File

@ -1,4 +1,4 @@
package main
package main
import (
"flag"
@ -8,6 +8,8 @@ import (
"os/signal"
"syscall"
"golang.org/x/net/context"
"github.com/go-kit/kit/examples/profilesvc"
"github.com/go-kit/kit/log"
)
@ -21,8 +23,13 @@ func main() {
var logger log.Logger
{
logger = log.NewLogfmtLogger(os.Stderr)
logger = log.With(logger, "ts", log.DefaultTimestampUTC)
logger = log.With(logger, "caller", log.DefaultCaller)
logger = log.NewContext(logger).With("ts", log.DefaultTimestampUTC)
logger = log.NewContext(logger).With("caller", log.DefaultCaller)
}
var ctx context.Context
{
ctx = context.Background()
}
var s profilesvc.Service
@ -33,7 +40,7 @@ func main() {
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)

View File

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

View File

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

View File

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

View File

@ -4,14 +4,15 @@ package profilesvc
import (
"bytes"
"context"
"encoding/json"
"errors"
"io/ioutil"
"net/http"
"net/url"
"github.com/gorilla/mux"
"golang.org/x/net/context"
"github.com/go-kit/kit/log"
httptransport "github.com/go-kit/kit/transport/http"
@ -25,7 +26,7 @@ var (
// MakeHTTPHandler mounts all of the service endpoints into an http.Handler.
// 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()
e := MakeServerEndpoints(s)
options := []httptransport.ServerOption{
@ -44,54 +45,63 @@ func MakeHTTPHandler(s Service, logger log.Logger) http.Handler {
// DELETE /profiles/:id/addresses/:addressID remove an address
r.Methods("POST").Path("/profiles/").Handler(httptransport.NewServer(
ctx,
e.PostProfileEndpoint,
decodePostProfileRequest,
encodeResponse,
options...,
))
r.Methods("GET").Path("/profiles/{id}").Handler(httptransport.NewServer(
ctx,
e.GetProfileEndpoint,
decodeGetProfileRequest,
encodeResponse,
options...,
))
r.Methods("PUT").Path("/profiles/{id}").Handler(httptransport.NewServer(
ctx,
e.PutProfileEndpoint,
decodePutProfileRequest,
encodeResponse,
options...,
))
r.Methods("PATCH").Path("/profiles/{id}").Handler(httptransport.NewServer(
ctx,
e.PatchProfileEndpoint,
decodePatchProfileRequest,
encodeResponse,
options...,
))
r.Methods("DELETE").Path("/profiles/{id}").Handler(httptransport.NewServer(
ctx,
e.DeleteProfileEndpoint,
decodeDeleteProfileRequest,
encodeResponse,
options...,
))
r.Methods("GET").Path("/profiles/{id}/addresses/").Handler(httptransport.NewServer(
ctx,
e.GetAddressesEndpoint,
decodeGetAddressesRequest,
encodeResponse,
options...,
))
r.Methods("GET").Path("/profiles/{id}/addresses/{addressID}").Handler(httptransport.NewServer(
ctx,
e.GetAddressEndpoint,
decodeGetAddressRequest,
encodeResponse,
options...,
))
r.Methods("POST").Path("/profiles/{id}/addresses/").Handler(httptransport.NewServer(
ctx,
e.PostAddressEndpoint,
decodePostAddressRequest,
encodeResponse,
options...,
))
r.Methods("DELETE").Path("/profiles/{id}/addresses/{addressID}").Handler(httptransport.NewServer(
ctx,
e.DeleteAddressEndpoint,
decodeDeleteAddressRequest,
encodeResponse,
@ -394,7 +404,17 @@ func codeFrom(err error) int {
return http.StatusNotFound
case ErrAlreadyExists, ErrInconsistentIDs:
return http.StatusBadRequest
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
}
}

View File

@ -1,9 +1,10 @@
package booking
import (
"context"
"time"
"golang.org/x/net/context"
"github.com/go-kit/kit/endpoint"
"github.com/go-kit/kit/examples/shipping/cargo"
@ -42,10 +43,10 @@ type loadCargoResponse struct {
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) {
req := request.(loadCargoRequest)
c, err := s.LoadCargo(req.ID)
c, err := bs.LoadCargo(req.ID)
return loadCargoResponse{Cargo: &c, Err: err}, nil
}
}

View File

@ -1,13 +1,13 @@
package booking
import (
"context"
"encoding/json"
"errors"
"net/http"
"time"
"github.com/gorilla/mux"
"golang.org/x/net/context"
kitlog "github.com/go-kit/kit/log"
kithttp "github.com/go-kit/kit/transport/http"
@ -17,49 +17,56 @@ import (
)
// 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{
kithttp.ServerErrorLogger(logger),
kithttp.ServerErrorEncoder(encodeError),
}
bookCargoHandler := kithttp.NewServer(
ctx,
makeBookCargoEndpoint(bs),
decodeBookCargoRequest,
encodeResponse,
opts...,
)
loadCargoHandler := kithttp.NewServer(
ctx,
makeLoadCargoEndpoint(bs),
decodeLoadCargoRequest,
encodeResponse,
opts...,
)
requestRoutesHandler := kithttp.NewServer(
ctx,
makeRequestRoutesEndpoint(bs),
decodeRequestRoutesRequest,
encodeResponse,
opts...,
)
assignToRouteHandler := kithttp.NewServer(
ctx,
makeAssignToRouteEndpoint(bs),
decodeAssignToRouteRequest,
encodeResponse,
opts...,
)
changeDestinationHandler := kithttp.NewServer(
ctx,
makeChangeDestinationEndpoint(bs),
decodeChangeDestinationRequest,
encodeResponse,
opts...,
)
listCargosHandler := kithttp.NewServer(
ctx,
makeListCargosEndpoint(bs),
decodeListCargosRequest,
encodeResponse,
opts...,
)
listLocationsHandler := kithttp.NewServer(
ctx,
makeListLocationsEndpoint(bs),
decodeListLocationsRequest,
encodeResponse,

View File

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

View File

@ -1,12 +1,12 @@
package handling
import (
"context"
"encoding/json"
"net/http"
"time"
"github.com/gorilla/mux"
"golang.org/x/net/context"
kitlog "github.com/go-kit/kit/log"
kithttp "github.com/go-kit/kit/transport/http"
@ -17,7 +17,7 @@ import (
)
// 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()
opts := []kithttp.ServerOption{
@ -26,6 +26,7 @@ func MakeHandler(hs Service, logger kitlog.Logger) http.Handler {
}
registerIncidentHandler := kithttp.NewServer(
ctx,
makeRegisterIncidentEndpoint(hs),
decodeRegisterIncidentRequest,
encodeResponse,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,12 +1,12 @@
package tracking
import (
"context"
"encoding/json"
"errors"
"net/http"
"github.com/gorilla/mux"
"golang.org/x/net/context"
kitlog "github.com/go-kit/kit/log"
kithttp "github.com/go-kit/kit/transport/http"
@ -15,7 +15,7 @@ import (
)
// 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()
opts := []kithttp.ServerOption{
@ -24,6 +24,7 @@ func MakeHandler(ts Service, logger kitlog.Logger) http.Handler {
}
trackCargoHandler := kithttp.NewServer(
ctx,
makeTrackCargoEndpoint(ts),
decodeTrackCargoRequest,
encodeResponse,

View File

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

View File

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

View File

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

View File

@ -1,12 +1,12 @@
package main
import (
"context"
"flag"
"net/http"
"os"
stdprometheus "github.com/prometheus/client_golang/prometheus"
"golang.org/x/net/context"
"github.com/go-kit/kit/log"
kitprometheus "github.com/go-kit/kit/metrics/prometheus"
@ -22,7 +22,9 @@ func main() {
var logger log.Logger
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"}
requestCount := kitprometheus.NewCounterFrom(stdprometheus.CounterOpts{
@ -46,16 +48,18 @@ func main() {
var svc StringService
svc = stringService{}
svc = proxyingMiddleware(context.Background(), *proxy, logger)(svc)
svc = proxyingMiddleware(*proxy, ctx, logger)(svc)
svc = loggingMiddleware(logger)(svc)
svc = instrumentingMiddleware(requestCount, requestLatency, countResult)(svc)
uppercaseHandler := httptransport.NewServer(
ctx,
makeUppercaseEndpoint(svc),
decodeUppercaseRequest,
encodeResponse,
)
countHandler := httptransport.NewServer(
ctx,
makeCountEndpoint(svc),
decodeCountRequest,
encodeResponse,

View File

@ -1,7 +1,6 @@
package main
import (
"context"
"errors"
"fmt"
"net/url"
@ -10,6 +9,7 @@ import (
jujuratelimit "github.com/juju/ratelimit"
"github.com/sony/gobreaker"
"golang.org/x/net/context"
"github.com/go-kit/kit/circuitbreaker"
"github.com/go-kit/kit/endpoint"
@ -20,7 +20,7 @@ import (
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 == "" {
logger.Log("proxy_to", "none")

View File

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

View File

@ -1,22 +1,20 @@
# package log
`package log` provides a minimal interface for structured logging in services.
It may be wrapped to encode conventions, enforce type-safety, provide leveled
logging, and so on. It can be used for both typical application log events,
and log-structured data streams.
It may be wrapped to encode conventions, enforce type-safety, provide leveled logging, and so on.
It can be used for both typical application log events, and log-structured data streams.
## Structured logging
Structured logging is, basically, conceding to the reality that logs are
_data_, and warrant some level of schematic rigor. Using a stricter,
key/value-oriented message format for our logs, containing contextual and
semantic information, makes it much easier to get insight into the
operational activity of the systems we build. Consequently, `package log` is
of the strong belief that "[the benefits of structured logging outweigh the
minimal effort involved](https://www.thoughtworks.com/radar/techniques/structured-logging)".
Structured logging is, basically, conceding to the reality that logs are _data_,
and warrant some level of schematic rigor.
Using a stricter, key/value-oriented message format for our logs,
containing contextual and semantic information,
makes it much easier to get insight into the operational activity of the systems we build.
Consequently, `package log` is of the strong belief that
"[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
than you'd expect.
Migrating from unstructured to structured logging is probably a lot easier than you'd expect.
```go
// 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
```
### Contextual Loggers
### Log contexts
```go
func main() {
var logger log.Logger
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")
NewWorker(log.With(logger, "component", "worker")).Run()
NewSlacker(log.With(logger, "component", "slacker")).Run()
NewWorker(log.NewContext(logger).With("component", "worker")).Run()
NewSlacker(log.NewContext(logger).With("component", "slacker")).Run()
}
// Output:
@ -79,8 +77,9 @@ func main() {
// {"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
stdlib log package, you can redirect Go kit logger to the stdlib logger.
Or, if, for legacy reasons,
you need to pipe all of your logging through the stdlib log package,
you can redirect Go kit logger to the stdlib logger.
```go
logger := kitlog.NewLogfmtLogger(kitlog.StdlibWriter{})
@ -95,7 +94,7 @@ logger.Log("legacy", true, "msg", "at least it's something")
```go
var logger log.Logger
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")
@ -105,7 +104,7 @@ logger.Log("msg", "hello")
## 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
## Enhancements
@ -118,8 +117,9 @@ type Logger interface {
}
```
This interface, and its supporting code like is the product of much iteration
and evaluation. For more details on the evolution of the Logger interface,
This interface, and its supporting code like [log.Context](https://godoc.org/github.com/go-kit/kit/log#Context),
is the product of much iteration and evaluation.
For more details on the evolution of the Logger interface,
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
@ -132,11 +132,12 @@ Also, please see
to review historical conversations about package log and the Logger interface.
Value-add packages and suggestions,
like improvements to [the leveled logger](https://godoc.org/github.com/go-kit/kit/log/level),
are of course welcome. Good proposals should
like improvements to [the leveled logger](https://godoc.org/github.com/go-kit/kit/log/levels),
are of course welcome.
Good proposals should
- Be composable with [contextual loggers](https://godoc.org/github.com/go-kit/kit/log#With),
- Not break the behavior of [log.Caller](https://godoc.org/github.com/go-kit/kit/log#Caller) in any wrapped contextual loggers, and
- 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 context, and
- Be friendly to packages that accept only an unadorned log.Logger.
## Benchmarks & comparisons

View File

@ -7,7 +7,7 @@ import (
)
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.ResetTimer()
for i := 0; i < b.N; i++ {
@ -17,5 +17,5 @@ func benchmarkRunner(b *testing.B, logger log.Logger, f func(log.Logger)) {
var (
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
// 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.
// Building appropriate contextual loggers reduces repetition and aids
// consistency in the resulting log output. With and WithPrefix add context to
// a logger. We can use With to improve the RunTask example.
// A log context stores keyvals that it includes in all log events. Building
// appropriate log contexts reduces repetition and aids consistency in the
// resulting log output. We can use a context to improve the RunTask example.
//
// 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")
// ...
// taskHelper(task.Cmd, logger)
@ -52,18 +51,19 @@
// }
//
// 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
// enables each log event created by taskHelper to include the task.ID even
// though taskHelper does not have access to that value. Using contextual
// loggers this way simplifies producing log output that enables tracing the
// life cycle of individual tasks. (See the Contextual example for the full
// code of the above snippet.)
// first and last calls to Log. The call to taskHelper highlights that a
// context may be passed as a logger to other functions. Each log event
// created by the called function will include the task.ID even though the
// function does not have access to that value. Using log contexts this way
// simplifies producing log output that enables tracing the life cycle of
// 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
// time an event is logged. The Valuer example demonstrates how this feature
// works.
// A Valuer function stored in a log context generates a new value each time
// the context logs an event. The Valuer example demonstrates how this
// feature works.
//
// Valuers provide the basis for consistently logging timestamps and source
// 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:
//
// 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
//
@ -90,27 +90,4 @@
// handled atomically within the wrapped logger, but it typically serializes
// both the formatting and output logic. Use a SyncLogger if the formatting
// 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

View File

@ -1,9 +1,7 @@
package log_test
import (
"math/rand"
"os"
"sync"
"time"
"github.com/go-kit/kit/log"
@ -29,7 +27,7 @@ func Example_basic() {
// taskID=1 event="task complete"
}
func Example_contextual() {
func Example_context() {
logger := log.NewLogfmtLogger(os.Stdout)
type Task struct {
@ -43,7 +41,7 @@ func Example_contextual() {
}
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")
taskHelper(task.Cmd, logger)
@ -68,7 +66,7 @@ func Example_valuer() {
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", "second")
@ -88,7 +86,7 @@ func Example_debugInfo() {
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", "second")
@ -98,40 +96,7 @@ func Example_debugInfo() {
logger.Log("call", "third")
// Output:
// time=2015-02-03T10:00:01Z caller=example_test.go:93 call=first
// time=2015-02-03T10:00:02Z caller=example_test.go:94 call=second
// time=2015-02-03T10:00:03Z caller=example_test.go:98 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"
// time=2015-02-03T10:00:01Z caller=example_test.go:91 call=first
// time=2015-02-03T10:00:02Z caller=example_test.go:92 call=second
// time=2015-02-03T10:00:03Z caller=example_test.go:96 call=third
}

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()
buf := &bytes.Buffer{}
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 {
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)
}
}

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