mirror of https://github.com/schollz/croc.git
re-vendor
This commit is contained in:
parent
a29a7e2f04
commit
04dc07913e
|
@ -1,6 +1,12 @@
|
|||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/Nightbug/go-base65536"
|
||||
packages = ["."]
|
||||
revision = "ba82e75856c3f7d4db92c6d98246cb4f7e9c1da8"
|
||||
version = "1.0.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/cihub/seelog"
|
||||
packages = ["."]
|
||||
|
@ -37,6 +43,18 @@
|
|||
packages = ["."]
|
||||
revision = "65899cf653ff022fe5c7fe504b439feed9e7e0fc"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/mitchellh/colorstring"
|
||||
packages = ["."]
|
||||
revision = "8631ce90f28644f54aeedcb3e389a85174e067d1"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/mr-tron/base58"
|
||||
packages = ["base58"]
|
||||
revision = "4df4dc6e86a912614d09719d10cad427b087cbfb"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/pkg/errors"
|
||||
packages = ["."]
|
||||
|
@ -45,27 +63,33 @@
|
|||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/schollz/messagebox"
|
||||
packages = ["keypair"]
|
||||
revision = "8bb214da539adf4b51b2c4ed0456e771d032d8eb"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/schollz/mnemonicode"
|
||||
packages = ["."]
|
||||
revision = "15c9654387fad6d257aa28f9be57b9f124101955"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/schollz/peerdiscovery"
|
||||
packages = ["."]
|
||||
revision = "94ddd4cf05a8a6052e334c69ac1cd86486de10af"
|
||||
version = "v1.1.0"
|
||||
revision = "2c99137c9f8ff3597c2325c3be232612867652e3"
|
||||
version = "v1.2.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/schollz/progressbar"
|
||||
packages = ["."]
|
||||
revision = "5441d79e9e64d6356575e1e296142ebba27949a7"
|
||||
version = "v0.6.0"
|
||||
revision = "cd5f4bc33956d41669a8920edb38a0f41abde270"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/schollz/tarinator-go"
|
||||
packages = ["."]
|
||||
revision = "a8626a55d48dbb55141e51226eefa15c157a65a8"
|
||||
version = "v0.3.3"
|
||||
revision = "0eab2c92d3ce5cb04535445b0825b2120c4acf3f"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/sirupsen/logrus"
|
||||
|
@ -88,26 +112,48 @@
|
|||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/yudai/hcl"
|
||||
packages = [".","hcl","json"]
|
||||
packages = [
|
||||
".",
|
||||
"hcl",
|
||||
"json"
|
||||
]
|
||||
revision = "5fa2393b3552119bf33a69adb1402a1160cba23d"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/crypto"
|
||||
packages = ["pbkdf2","scrypt","ssh/terminal"]
|
||||
revision = "2b6c08872f4b66da917bb4ce98df4f0307330f78"
|
||||
packages = [
|
||||
"curve25519",
|
||||
"internal/subtle",
|
||||
"nacl/box",
|
||||
"nacl/secretbox",
|
||||
"pbkdf2",
|
||||
"poly1305",
|
||||
"salsa20/salsa",
|
||||
"scrypt",
|
||||
"ssh/terminal"
|
||||
]
|
||||
revision = "a49355c7e3f8fe157a85be2f77e6e269a0f89602"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/net"
|
||||
packages = ["bpf","internal/iana","internal/socket","ipv4"]
|
||||
revision = "5f9ae10d9af5b1c89ae6904293b14b064d4ada23"
|
||||
packages = [
|
||||
"bpf",
|
||||
"internal/iana",
|
||||
"internal/socket",
|
||||
"ipv4"
|
||||
]
|
||||
revision = "afe8f62b1d6bbd81f31868121a50b06d8188e1f9"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/sys"
|
||||
packages = ["unix","windows"]
|
||||
revision = "79b0c6888797020a994db17c8510466c72fe75d9"
|
||||
packages = [
|
||||
"unix",
|
||||
"windows"
|
||||
]
|
||||
revision = "63fc586f45fe72d95d5240a5d5eb95e6503907d3"
|
||||
|
||||
[[projects]]
|
||||
name = "golang.org/x/text"
|
||||
|
@ -124,6 +170,6 @@
|
|||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "93fd6df35a71aeabf37bf934c95bab3e099b86f74ad0479e0ce491695a996527"
|
||||
inputs-digest = "61c77dae1b7a75c24ba182fc04218815b058280c4a96d39a8b16d3a851ecbbc6"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
|
|
24
Gopkg.toml
24
Gopkg.toml
|
@ -1,7 +1,6 @@
|
|||
|
||||
# Gopkg.toml example
|
||||
#
|
||||
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
|
||||
# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
|
||||
# for detailed Gopkg.toml documentation.
|
||||
#
|
||||
# required = ["github.com/user/thing/cmd/thing"]
|
||||
|
@ -19,6 +18,11 @@
|
|||
# [[override]]
|
||||
# name = "github.com/x/y"
|
||||
# version = "2.4.0"
|
||||
#
|
||||
# [prune]
|
||||
# non-go = false
|
||||
# go-tests = true
|
||||
# unused-packages = true
|
||||
|
||||
|
||||
[[constraint]]
|
||||
|
@ -43,19 +47,23 @@
|
|||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/schollz/mnemonicode"
|
||||
name = "github.com/schollz/messagebox"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/schollz/mnemonicode"
|
||||
version = "1.0.0"
|
||||
|
||||
[[constraint]]
|
||||
version = "1.1.0"
|
||||
name = "github.com/schollz/peerdiscovery"
|
||||
branch = "master"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/schollz/progressbar"
|
||||
version = "0.6.0"
|
||||
branch = "master"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/schollz/tarinator-go"
|
||||
version = "0.3.3"
|
||||
branch = "master"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/sirupsen/logrus"
|
||||
|
@ -80,3 +88,7 @@
|
|||
[[constraint]]
|
||||
name = "gopkg.in/yaml.v2"
|
||||
version = "2.2.1"
|
||||
|
||||
[prune]
|
||||
go-tests = true
|
||||
unused-packages = true
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
# go-base65536 [![GoDoc](https://godoc.org/github.com/Nightbug/go-base65536?status.svg)](https://godoc.org/github.com/Nightbug/go-base65536)
|
||||
|
||||
Go library for encoding data into [base65536](https://github.com/ferno/base65536).
|
||||
|
||||
## Examples
|
||||
|
||||
Marshaling
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Nightbug/go-base65536"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Println(base65536.Marshal([]byte("hello world")))
|
||||
}
|
||||
```
|
||||
|
||||
Unmarshaling
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Nightbug/go-base65536"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var out []byte
|
||||
err := base65536.Marshal([]byte("驨ꍬ啯𒁷ꍲᕤ"), &out)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
|
@ -0,0 +1,56 @@
|
|||
package base65536
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
const noByte = -1
|
||||
|
||||
// Marshal returns the base65536 encoded version of data.
|
||||
func Marshal(data []byte) string {
|
||||
var res string
|
||||
var b1 int16
|
||||
var b2 int16
|
||||
for i := 0; i < len(data); i += 2 {
|
||||
b1 = int16(data[i])
|
||||
if i+1 < len(data) {
|
||||
b2 = int16(data[i+1])
|
||||
} else {
|
||||
b2 = noByte
|
||||
}
|
||||
res += string(getBlockStart[b2] + int(b1))
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// Unmarshal appends to out the bytes decoded from data. It can return an error,
|
||||
// in case the decoding failed.
|
||||
func Unmarshal(data []byte, out *[]byte) error {
|
||||
done := false
|
||||
var b1 int16
|
||||
var b2 int16
|
||||
var exists bool
|
||||
var bytesAppend []byte
|
||||
// We are converting data to a string, because runes
|
||||
for _, r := range string(data) {
|
||||
b1 = int16(r) & ((1 << 8) - 1)
|
||||
b2, exists = getB2[int(r)-int(b1)]
|
||||
if !exists {
|
||||
return errors.New("not a valid base65536 code point: " + string(int(r)))
|
||||
}
|
||||
bytesAppend = []byte{
|
||||
byte(b1),
|
||||
}
|
||||
if b2 != noByte {
|
||||
bytesAppend = append(bytesAppend, byte(b2))
|
||||
}
|
||||
if len(bytesAppend) == 1 {
|
||||
if done {
|
||||
return errors.New("base65536 sequence continued after final byte")
|
||||
}
|
||||
done = true
|
||||
}
|
||||
*out = append(*out, bytesAppend...)
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,261 @@
|
|||
package base65536
|
||||
|
||||
var getB2 = map[int]int16{
|
||||
5376: -1,
|
||||
13312: 0,
|
||||
13568: 1,
|
||||
13824: 2,
|
||||
14080: 3,
|
||||
14336: 4,
|
||||
14592: 5,
|
||||
14848: 6,
|
||||
15104: 7,
|
||||
15360: 8,
|
||||
15616: 9,
|
||||
15872: 10,
|
||||
16128: 11,
|
||||
16384: 12,
|
||||
16640: 13,
|
||||
16896: 14,
|
||||
17152: 15,
|
||||
17408: 16,
|
||||
17664: 17,
|
||||
17920: 18,
|
||||
18176: 19,
|
||||
18432: 20,
|
||||
18688: 21,
|
||||
18944: 22,
|
||||
19200: 23,
|
||||
19456: 24,
|
||||
19968: 25,
|
||||
20224: 26,
|
||||
20480: 27,
|
||||
20736: 28,
|
||||
20992: 29,
|
||||
21248: 30,
|
||||
21504: 31,
|
||||
21760: 32,
|
||||
22016: 33,
|
||||
22272: 34,
|
||||
22528: 35,
|
||||
22784: 36,
|
||||
23040: 37,
|
||||
23296: 38,
|
||||
23552: 39,
|
||||
23808: 40,
|
||||
24064: 41,
|
||||
24320: 42,
|
||||
24576: 43,
|
||||
24832: 44,
|
||||
25088: 45,
|
||||
25344: 46,
|
||||
25600: 47,
|
||||
25856: 48,
|
||||
26112: 49,
|
||||
26368: 50,
|
||||
26624: 51,
|
||||
26880: 52,
|
||||
27136: 53,
|
||||
27392: 54,
|
||||
27648: 55,
|
||||
27904: 56,
|
||||
28160: 57,
|
||||
28416: 58,
|
||||
28672: 59,
|
||||
28928: 60,
|
||||
29184: 61,
|
||||
29440: 62,
|
||||
29696: 63,
|
||||
29952: 64,
|
||||
30208: 65,
|
||||
30464: 66,
|
||||
30720: 67,
|
||||
30976: 68,
|
||||
31232: 69,
|
||||
31488: 70,
|
||||
31744: 71,
|
||||
32000: 72,
|
||||
32256: 73,
|
||||
32512: 74,
|
||||
32768: 75,
|
||||
33024: 76,
|
||||
33280: 77,
|
||||
33536: 78,
|
||||
33792: 79,
|
||||
34048: 80,
|
||||
34304: 81,
|
||||
34560: 82,
|
||||
34816: 83,
|
||||
35072: 84,
|
||||
35328: 85,
|
||||
35584: 86,
|
||||
35840: 87,
|
||||
36096: 88,
|
||||
36352: 89,
|
||||
36608: 90,
|
||||
36864: 91,
|
||||
37120: 92,
|
||||
37376: 93,
|
||||
37632: 94,
|
||||
37888: 95,
|
||||
38144: 96,
|
||||
38400: 97,
|
||||
38656: 98,
|
||||
38912: 99,
|
||||
39168: 100,
|
||||
39424: 101,
|
||||
39680: 102,
|
||||
39936: 103,
|
||||
40192: 104,
|
||||
40448: 105,
|
||||
41216: 106,
|
||||
41472: 107,
|
||||
41728: 108,
|
||||
42240: 109,
|
||||
67072: 110,
|
||||
73728: 111,
|
||||
73984: 112,
|
||||
74240: 113,
|
||||
77824: 114,
|
||||
78080: 115,
|
||||
78336: 116,
|
||||
78592: 117,
|
||||
82944: 118,
|
||||
83200: 119,
|
||||
92160: 120,
|
||||
92416: 121,
|
||||
131072: 122,
|
||||
131328: 123,
|
||||
131584: 124,
|
||||
131840: 125,
|
||||
132096: 126,
|
||||
132352: 127,
|
||||
132608: 128,
|
||||
132864: 129,
|
||||
133120: 130,
|
||||
133376: 131,
|
||||
133632: 132,
|
||||
133888: 133,
|
||||
134144: 134,
|
||||
134400: 135,
|
||||
134656: 136,
|
||||
134912: 137,
|
||||
135168: 138,
|
||||
135424: 139,
|
||||
135680: 140,
|
||||
135936: 141,
|
||||
136192: 142,
|
||||
136448: 143,
|
||||
136704: 144,
|
||||
136960: 145,
|
||||
137216: 146,
|
||||
137472: 147,
|
||||
137728: 148,
|
||||
137984: 149,
|
||||
138240: 150,
|
||||
138496: 151,
|
||||
138752: 152,
|
||||
139008: 153,
|
||||
139264: 154,
|
||||
139520: 155,
|
||||
139776: 156,
|
||||
140032: 157,
|
||||
140288: 158,
|
||||
140544: 159,
|
||||
140800: 160,
|
||||
141056: 161,
|
||||
141312: 162,
|
||||
141568: 163,
|
||||
141824: 164,
|
||||
142080: 165,
|
||||
142336: 166,
|
||||
142592: 167,
|
||||
142848: 168,
|
||||
143104: 169,
|
||||
143360: 170,
|
||||
143616: 171,
|
||||
143872: 172,
|
||||
144128: 173,
|
||||
144384: 174,
|
||||
144640: 175,
|
||||
144896: 176,
|
||||
145152: 177,
|
||||
145408: 178,
|
||||
145664: 179,
|
||||
145920: 180,
|
||||
146176: 181,
|
||||
146432: 182,
|
||||
146688: 183,
|
||||
146944: 184,
|
||||
147200: 185,
|
||||
147456: 186,
|
||||
147712: 187,
|
||||
147968: 188,
|
||||
148224: 189,
|
||||
148480: 190,
|
||||
148736: 191,
|
||||
148992: 192,
|
||||
149248: 193,
|
||||
149504: 194,
|
||||
149760: 195,
|
||||
150016: 196,
|
||||
150272: 197,
|
||||
150528: 198,
|
||||
150784: 199,
|
||||
151040: 200,
|
||||
151296: 201,
|
||||
151552: 202,
|
||||
151808: 203,
|
||||
152064: 204,
|
||||
152320: 205,
|
||||
152576: 206,
|
||||
152832: 207,
|
||||
153088: 208,
|
||||
153344: 209,
|
||||
153600: 210,
|
||||
153856: 211,
|
||||
154112: 212,
|
||||
154368: 213,
|
||||
154624: 214,
|
||||
154880: 215,
|
||||
155136: 216,
|
||||
155392: 217,
|
||||
155648: 218,
|
||||
155904: 219,
|
||||
156160: 220,
|
||||
156416: 221,
|
||||
156672: 222,
|
||||
156928: 223,
|
||||
157184: 224,
|
||||
157440: 225,
|
||||
157696: 226,
|
||||
157952: 227,
|
||||
158208: 228,
|
||||
158464: 229,
|
||||
158720: 230,
|
||||
158976: 231,
|
||||
159232: 232,
|
||||
159488: 233,
|
||||
159744: 234,
|
||||
160000: 235,
|
||||
160256: 236,
|
||||
160512: 237,
|
||||
160768: 238,
|
||||
161024: 239,
|
||||
161280: 240,
|
||||
161536: 241,
|
||||
161792: 242,
|
||||
162048: 243,
|
||||
162304: 244,
|
||||
162560: 245,
|
||||
162816: 246,
|
||||
163072: 247,
|
||||
163328: 248,
|
||||
163584: 249,
|
||||
163840: 250,
|
||||
164096: 251,
|
||||
164352: 252,
|
||||
164608: 253,
|
||||
164864: 254,
|
||||
165120: 255,
|
||||
}
|
|
@ -0,0 +1,261 @@
|
|||
package base65536
|
||||
|
||||
var getBlockStart = map[int16]int{
|
||||
-1: 5376,
|
||||
0: 13312,
|
||||
1: 13568,
|
||||
2: 13824,
|
||||
3: 14080,
|
||||
4: 14336,
|
||||
5: 14592,
|
||||
6: 14848,
|
||||
7: 15104,
|
||||
8: 15360,
|
||||
9: 15616,
|
||||
10: 15872,
|
||||
11: 16128,
|
||||
12: 16384,
|
||||
13: 16640,
|
||||
14: 16896,
|
||||
15: 17152,
|
||||
16: 17408,
|
||||
17: 17664,
|
||||
18: 17920,
|
||||
19: 18176,
|
||||
20: 18432,
|
||||
21: 18688,
|
||||
22: 18944,
|
||||
23: 19200,
|
||||
24: 19456,
|
||||
25: 19968,
|
||||
26: 20224,
|
||||
27: 20480,
|
||||
28: 20736,
|
||||
29: 20992,
|
||||
30: 21248,
|
||||
31: 21504,
|
||||
32: 21760,
|
||||
33: 22016,
|
||||
34: 22272,
|
||||
35: 22528,
|
||||
36: 22784,
|
||||
37: 23040,
|
||||
38: 23296,
|
||||
39: 23552,
|
||||
40: 23808,
|
||||
41: 24064,
|
||||
42: 24320,
|
||||
43: 24576,
|
||||
44: 24832,
|
||||
45: 25088,
|
||||
46: 25344,
|
||||
47: 25600,
|
||||
48: 25856,
|
||||
49: 26112,
|
||||
50: 26368,
|
||||
51: 26624,
|
||||
52: 26880,
|
||||
53: 27136,
|
||||
54: 27392,
|
||||
55: 27648,
|
||||
56: 27904,
|
||||
57: 28160,
|
||||
58: 28416,
|
||||
59: 28672,
|
||||
60: 28928,
|
||||
61: 29184,
|
||||
62: 29440,
|
||||
63: 29696,
|
||||
64: 29952,
|
||||
65: 30208,
|
||||
66: 30464,
|
||||
67: 30720,
|
||||
68: 30976,
|
||||
69: 31232,
|
||||
70: 31488,
|
||||
71: 31744,
|
||||
72: 32000,
|
||||
73: 32256,
|
||||
74: 32512,
|
||||
75: 32768,
|
||||
76: 33024,
|
||||
77: 33280,
|
||||
78: 33536,
|
||||
79: 33792,
|
||||
80: 34048,
|
||||
81: 34304,
|
||||
82: 34560,
|
||||
83: 34816,
|
||||
84: 35072,
|
||||
85: 35328,
|
||||
86: 35584,
|
||||
87: 35840,
|
||||
88: 36096,
|
||||
89: 36352,
|
||||
90: 36608,
|
||||
91: 36864,
|
||||
92: 37120,
|
||||
93: 37376,
|
||||
94: 37632,
|
||||
95: 37888,
|
||||
96: 38144,
|
||||
97: 38400,
|
||||
98: 38656,
|
||||
99: 38912,
|
||||
100: 39168,
|
||||
101: 39424,
|
||||
102: 39680,
|
||||
103: 39936,
|
||||
104: 40192,
|
||||
105: 40448,
|
||||
106: 41216,
|
||||
107: 41472,
|
||||
108: 41728,
|
||||
109: 42240,
|
||||
110: 67072,
|
||||
111: 73728,
|
||||
112: 73984,
|
||||
113: 74240,
|
||||
114: 77824,
|
||||
115: 78080,
|
||||
116: 78336,
|
||||
117: 78592,
|
||||
118: 82944,
|
||||
119: 83200,
|
||||
120: 92160,
|
||||
121: 92416,
|
||||
122: 131072,
|
||||
123: 131328,
|
||||
124: 131584,
|
||||
125: 131840,
|
||||
126: 132096,
|
||||
127: 132352,
|
||||
128: 132608,
|
||||
129: 132864,
|
||||
130: 133120,
|
||||
131: 133376,
|
||||
132: 133632,
|
||||
133: 133888,
|
||||
134: 134144,
|
||||
135: 134400,
|
||||
136: 134656,
|
||||
137: 134912,
|
||||
138: 135168,
|
||||
139: 135424,
|
||||
140: 135680,
|
||||
141: 135936,
|
||||
142: 136192,
|
||||
143: 136448,
|
||||
144: 136704,
|
||||
145: 136960,
|
||||
146: 137216,
|
||||
147: 137472,
|
||||
148: 137728,
|
||||
149: 137984,
|
||||
150: 138240,
|
||||
151: 138496,
|
||||
152: 138752,
|
||||
153: 139008,
|
||||
154: 139264,
|
||||
155: 139520,
|
||||
156: 139776,
|
||||
157: 140032,
|
||||
158: 140288,
|
||||
159: 140544,
|
||||
160: 140800,
|
||||
161: 141056,
|
||||
162: 141312,
|
||||
163: 141568,
|
||||
164: 141824,
|
||||
165: 142080,
|
||||
166: 142336,
|
||||
167: 142592,
|
||||
168: 142848,
|
||||
169: 143104,
|
||||
170: 143360,
|
||||
171: 143616,
|
||||
172: 143872,
|
||||
173: 144128,
|
||||
174: 144384,
|
||||
175: 144640,
|
||||
176: 144896,
|
||||
177: 145152,
|
||||
178: 145408,
|
||||
179: 145664,
|
||||
180: 145920,
|
||||
181: 146176,
|
||||
182: 146432,
|
||||
183: 146688,
|
||||
184: 146944,
|
||||
185: 147200,
|
||||
186: 147456,
|
||||
187: 147712,
|
||||
188: 147968,
|
||||
189: 148224,
|
||||
190: 148480,
|
||||
191: 148736,
|
||||
192: 148992,
|
||||
193: 149248,
|
||||
194: 149504,
|
||||
195: 149760,
|
||||
196: 150016,
|
||||
197: 150272,
|
||||
198: 150528,
|
||||
199: 150784,
|
||||
200: 151040,
|
||||
201: 151296,
|
||||
202: 151552,
|
||||
203: 151808,
|
||||
204: 152064,
|
||||
205: 152320,
|
||||
206: 152576,
|
||||
207: 152832,
|
||||
208: 153088,
|
||||
209: 153344,
|
||||
210: 153600,
|
||||
211: 153856,
|
||||
212: 154112,
|
||||
213: 154368,
|
||||
214: 154624,
|
||||
215: 154880,
|
||||
216: 155136,
|
||||
217: 155392,
|
||||
218: 155648,
|
||||
219: 155904,
|
||||
220: 156160,
|
||||
221: 156416,
|
||||
222: 156672,
|
||||
223: 156928,
|
||||
224: 157184,
|
||||
225: 157440,
|
||||
226: 157696,
|
||||
227: 157952,
|
||||
228: 158208,
|
||||
229: 158464,
|
||||
230: 158720,
|
||||
231: 158976,
|
||||
232: 159232,
|
||||
233: 159488,
|
||||
234: 159744,
|
||||
235: 160000,
|
||||
236: 160256,
|
||||
237: 160512,
|
||||
238: 160768,
|
||||
239: 161024,
|
||||
240: 161280,
|
||||
241: 161536,
|
||||
242: 161792,
|
||||
243: 162048,
|
||||
244: 162304,
|
||||
245: 162560,
|
||||
246: 162816,
|
||||
247: 163072,
|
||||
248: 163328,
|
||||
249: 163584,
|
||||
250: 163840,
|
||||
251: 164096,
|
||||
252: 164352,
|
||||
253: 164608,
|
||||
254: 164864,
|
||||
255: 165120,
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
Copyright (c) 2012, Cloud Instruments Co., Ltd. <info@cin.io>
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the Cloud Instruments Co., Ltd. nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,113 @@
|
|||
Seelog
|
||||
=======
|
||||
|
||||
Seelog is a powerful and easy-to-learn logging framework that provides functionality for flexible dispatching, filtering, and formatting log messages.
|
||||
It is natively written in the [Go](http://golang.org/) programming language.
|
||||
|
||||
[![Build Status](https://drone.io/github.com/cihub/seelog/status.png)](https://drone.io/github.com/cihub/seelog/latest)
|
||||
|
||||
Features
|
||||
------------------
|
||||
|
||||
* Xml configuring to be able to change logger parameters without recompilation
|
||||
* Changing configurations on the fly without app restart
|
||||
* Possibility to set different log configurations for different project files and functions
|
||||
* Adjustable message formatting
|
||||
* Simultaneous log output to multiple streams
|
||||
* Choosing logger priority strategy to minimize performance hit
|
||||
* Different output writers
|
||||
* Console writer
|
||||
* File writer
|
||||
* Buffered writer (Chunk writer)
|
||||
* Rolling log writer (Logging with rotation)
|
||||
* SMTP writer
|
||||
* Others... (See [Wiki](https://github.com/cihub/seelog/wiki))
|
||||
* Log message wrappers (JSON, XML, etc.)
|
||||
* Global variables and functions for easy usage in standalone apps
|
||||
* Functions for flexible usage in libraries
|
||||
|
||||
Quick-start
|
||||
-----------
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import log "github.com/cihub/seelog"
|
||||
|
||||
func main() {
|
||||
defer log.Flush()
|
||||
log.Info("Hello from Seelog!")
|
||||
}
|
||||
```
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
If you don't have the Go development environment installed, visit the
|
||||
[Getting Started](http://golang.org/doc/install.html) document and follow the instructions. Once you're ready, execute the following command:
|
||||
|
||||
```
|
||||
go get -u github.com/cihub/seelog
|
||||
```
|
||||
|
||||
*IMPORTANT*: If you are not using the latest release version of Go, check out this [wiki page](https://github.com/cihub/seelog/wiki/Notes-on-'go-get')
|
||||
|
||||
Documentation
|
||||
---------------
|
||||
|
||||
Seelog has github wiki pages, which contain detailed how-tos references: https://github.com/cihub/seelog/wiki
|
||||
|
||||
Examples
|
||||
---------------
|
||||
|
||||
Seelog examples can be found here: [seelog-examples](https://github.com/cihub/seelog-examples)
|
||||
|
||||
Issues
|
||||
---------------
|
||||
|
||||
Feel free to push issues that could make Seelog better: https://github.com/cihub/seelog/issues
|
||||
|
||||
Changelog
|
||||
---------------
|
||||
|
||||
* **v2.5** : Interaction with other systems. Part 2: custom receivers
|
||||
* Finished custom receivers feature. Check [wiki](https://github.com/cihub/seelog/wiki/custom-receivers)
|
||||
* Added 'LoggerFromCustomReceiver'
|
||||
* Added 'LoggerFromWriterWithMinLevelAndFormat'
|
||||
* Added 'LoggerFromCustomReceiver'
|
||||
* Added 'LoggerFromParamConfigAs...'
|
||||
* **v2.4** : Interaction with other systems. Part 1: wrapping seelog
|
||||
* Added configurable caller stack skip logic
|
||||
* Added 'SetAdditionalStackDepth' to 'LoggerInterface'
|
||||
* **v2.3** : Rethinking 'rolling' receiver
|
||||
* Reimplemented 'rolling' receiver
|
||||
* Added 'Max rolls' feature for 'rolling' receiver with type='date'
|
||||
* Fixed 'rolling' receiver issue: renaming on Windows
|
||||
* **v2.2** : go1.0 compatibility point [go1.0 tag]
|
||||
* Fixed internal bugs
|
||||
* Added 'ANSI n [;k]' format identifier: %EscN
|
||||
* Made current release go1 compatible
|
||||
* **v2.1** : Some new features
|
||||
* Rolling receiver archiving option.
|
||||
* Added format identifier: %Line
|
||||
* Smtp: added paths to PEM files directories
|
||||
* Added format identifier: %FuncShort
|
||||
* Warn, Error and Critical methods now return an error
|
||||
* **v2.0** : Second major release. BREAKING CHANGES.
|
||||
* Support of binaries with stripped symbols
|
||||
* Added log strategy: adaptive
|
||||
* Critical message now forces Flush()
|
||||
* Added predefined formats: xml-debug, xml-debug-short, xml, xml-short, json-debug, json-debug-short, json, json-short, debug, debug-short, fast
|
||||
* Added receiver: conn (network connection writer)
|
||||
* BREAKING CHANGE: added Tracef, Debugf, Infof, etc. to satisfy the print/printf principle
|
||||
* Bug fixes
|
||||
* **v1.0** : Initial release. Features:
|
||||
* Xml config
|
||||
* Changing configurations on the fly without app restart
|
||||
* Contraints and exceptions
|
||||
* Formatting
|
||||
* Log strategies: sync, async loop, async timer
|
||||
* Receivers: buffered, console, file, rolling, smtp
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||
// list of conditions and the following disclaimer.
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package seelog
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
adaptiveLoggerMaxInterval = time.Minute
|
||||
adaptiveLoggerMaxCriticalMsgCount = uint32(1000)
|
||||
)
|
||||
|
||||
// asyncAdaptiveLogger represents asynchronous adaptive logger which acts like
|
||||
// an async timer logger, but its interval depends on the current message count
|
||||
// in the queue.
|
||||
//
|
||||
// Interval = I, minInterval = m, maxInterval = M, criticalMsgCount = C, msgCount = c:
|
||||
// I = m + (C - Min(c, C)) / C * (M - m)
|
||||
type asyncAdaptiveLogger struct {
|
||||
asyncLogger
|
||||
minInterval time.Duration
|
||||
criticalMsgCount uint32
|
||||
maxInterval time.Duration
|
||||
}
|
||||
|
||||
// NewAsyncLoopLogger creates a new asynchronous adaptive logger
|
||||
func NewAsyncAdaptiveLogger(
|
||||
config *logConfig,
|
||||
minInterval time.Duration,
|
||||
maxInterval time.Duration,
|
||||
criticalMsgCount uint32) (*asyncAdaptiveLogger, error) {
|
||||
|
||||
if minInterval <= 0 {
|
||||
return nil, errors.New("async adaptive logger min interval should be > 0")
|
||||
}
|
||||
|
||||
if maxInterval > adaptiveLoggerMaxInterval {
|
||||
return nil, fmt.Errorf("async adaptive logger max interval should be <= %s",
|
||||
adaptiveLoggerMaxInterval)
|
||||
}
|
||||
|
||||
if criticalMsgCount <= 0 {
|
||||
return nil, errors.New("async adaptive logger critical msg count should be > 0")
|
||||
}
|
||||
|
||||
if criticalMsgCount > adaptiveLoggerMaxCriticalMsgCount {
|
||||
return nil, fmt.Errorf("async adaptive logger critical msg count should be <= %s",
|
||||
adaptiveLoggerMaxInterval)
|
||||
}
|
||||
|
||||
asnAdaptiveLogger := new(asyncAdaptiveLogger)
|
||||
|
||||
asnAdaptiveLogger.asyncLogger = *newAsyncLogger(config)
|
||||
asnAdaptiveLogger.minInterval = minInterval
|
||||
asnAdaptiveLogger.maxInterval = maxInterval
|
||||
asnAdaptiveLogger.criticalMsgCount = criticalMsgCount
|
||||
|
||||
go asnAdaptiveLogger.processQueue()
|
||||
|
||||
return asnAdaptiveLogger, nil
|
||||
}
|
||||
|
||||
func (asnAdaptiveLogger *asyncAdaptiveLogger) processItem() (closed bool, itemCount int) {
|
||||
asnAdaptiveLogger.queueHasElements.L.Lock()
|
||||
defer asnAdaptiveLogger.queueHasElements.L.Unlock()
|
||||
|
||||
for asnAdaptiveLogger.msgQueue.Len() == 0 && !asnAdaptiveLogger.Closed() {
|
||||
asnAdaptiveLogger.queueHasElements.Wait()
|
||||
}
|
||||
|
||||
if asnAdaptiveLogger.Closed() {
|
||||
return true, asnAdaptiveLogger.msgQueue.Len()
|
||||
}
|
||||
|
||||
asnAdaptiveLogger.processQueueElement()
|
||||
return false, asnAdaptiveLogger.msgQueue.Len() - 1
|
||||
}
|
||||
|
||||
// I = m + (C - Min(c, C)) / C * (M - m) =>
|
||||
// I = m + cDiff * mDiff,
|
||||
// cDiff = (C - Min(c, C)) / C)
|
||||
// mDiff = (M - m)
|
||||
func (asnAdaptiveLogger *asyncAdaptiveLogger) calcAdaptiveInterval(msgCount int) time.Duration {
|
||||
critCountF := float64(asnAdaptiveLogger.criticalMsgCount)
|
||||
cDiff := (critCountF - math.Min(float64(msgCount), critCountF)) / critCountF
|
||||
mDiff := float64(asnAdaptiveLogger.maxInterval - asnAdaptiveLogger.minInterval)
|
||||
|
||||
return asnAdaptiveLogger.minInterval + time.Duration(cDiff*mDiff)
|
||||
}
|
||||
|
||||
func (asnAdaptiveLogger *asyncAdaptiveLogger) processQueue() {
|
||||
for !asnAdaptiveLogger.Closed() {
|
||||
closed, itemCount := asnAdaptiveLogger.processItem()
|
||||
|
||||
if closed {
|
||||
break
|
||||
}
|
||||
|
||||
interval := asnAdaptiveLogger.calcAdaptiveInterval(itemCount)
|
||||
|
||||
<-time.After(interval)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,142 @@
|
|||
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||
// list of conditions and the following disclaimer.
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package seelog
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// MaxQueueSize is the critical number of messages in the queue that result in an immediate flush.
|
||||
const (
|
||||
MaxQueueSize = 10000
|
||||
)
|
||||
|
||||
type msgQueueItem struct {
|
||||
level LogLevel
|
||||
context LogContextInterface
|
||||
message fmt.Stringer
|
||||
}
|
||||
|
||||
// asyncLogger represents common data for all asynchronous loggers
|
||||
type asyncLogger struct {
|
||||
commonLogger
|
||||
msgQueue *list.List
|
||||
queueHasElements *sync.Cond
|
||||
}
|
||||
|
||||
// newAsyncLogger creates a new asynchronous logger
|
||||
func newAsyncLogger(config *logConfig) *asyncLogger {
|
||||
asnLogger := new(asyncLogger)
|
||||
|
||||
asnLogger.msgQueue = list.New()
|
||||
asnLogger.queueHasElements = sync.NewCond(new(sync.Mutex))
|
||||
|
||||
asnLogger.commonLogger = *newCommonLogger(config, asnLogger)
|
||||
|
||||
return asnLogger
|
||||
}
|
||||
|
||||
func (asnLogger *asyncLogger) innerLog(
|
||||
level LogLevel,
|
||||
context LogContextInterface,
|
||||
message fmt.Stringer) {
|
||||
|
||||
asnLogger.addMsgToQueue(level, context, message)
|
||||
}
|
||||
|
||||
func (asnLogger *asyncLogger) Close() {
|
||||
asnLogger.m.Lock()
|
||||
defer asnLogger.m.Unlock()
|
||||
|
||||
if !asnLogger.Closed() {
|
||||
asnLogger.flushQueue(true)
|
||||
asnLogger.config.RootDispatcher.Flush()
|
||||
|
||||
if err := asnLogger.config.RootDispatcher.Close(); err != nil {
|
||||
reportInternalError(err)
|
||||
}
|
||||
|
||||
asnLogger.closedM.Lock()
|
||||
asnLogger.closed = true
|
||||
asnLogger.closedM.Unlock()
|
||||
asnLogger.queueHasElements.Broadcast()
|
||||
}
|
||||
}
|
||||
|
||||
func (asnLogger *asyncLogger) Flush() {
|
||||
asnLogger.m.Lock()
|
||||
defer asnLogger.m.Unlock()
|
||||
|
||||
if !asnLogger.Closed() {
|
||||
asnLogger.flushQueue(true)
|
||||
asnLogger.config.RootDispatcher.Flush()
|
||||
}
|
||||
}
|
||||
|
||||
func (asnLogger *asyncLogger) flushQueue(lockNeeded bool) {
|
||||
if lockNeeded {
|
||||
asnLogger.queueHasElements.L.Lock()
|
||||
defer asnLogger.queueHasElements.L.Unlock()
|
||||
}
|
||||
|
||||
for asnLogger.msgQueue.Len() > 0 {
|
||||
asnLogger.processQueueElement()
|
||||
}
|
||||
}
|
||||
|
||||
func (asnLogger *asyncLogger) processQueueElement() {
|
||||
if asnLogger.msgQueue.Len() > 0 {
|
||||
backElement := asnLogger.msgQueue.Front()
|
||||
msg, _ := backElement.Value.(msgQueueItem)
|
||||
asnLogger.processLogMsg(msg.level, msg.message, msg.context)
|
||||
asnLogger.msgQueue.Remove(backElement)
|
||||
}
|
||||
}
|
||||
|
||||
func (asnLogger *asyncLogger) addMsgToQueue(
|
||||
level LogLevel,
|
||||
context LogContextInterface,
|
||||
message fmt.Stringer) {
|
||||
|
||||
if !asnLogger.Closed() {
|
||||
asnLogger.queueHasElements.L.Lock()
|
||||
defer asnLogger.queueHasElements.L.Unlock()
|
||||
|
||||
if asnLogger.msgQueue.Len() >= MaxQueueSize {
|
||||
fmt.Printf("Seelog queue overflow: more than %v messages in the queue. Flushing.\n", MaxQueueSize)
|
||||
asnLogger.flushQueue(false)
|
||||
}
|
||||
|
||||
queueItem := msgQueueItem{level, context, message}
|
||||
|
||||
asnLogger.msgQueue.PushBack(queueItem)
|
||||
asnLogger.queueHasElements.Broadcast()
|
||||
} else {
|
||||
err := fmt.Errorf("queue closed! Cannot process element: %d %#v", level, message)
|
||||
reportInternalError(err)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||
// list of conditions and the following disclaimer.
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package seelog
|
||||
|
||||
// asyncLoopLogger represents asynchronous logger which processes the log queue in
|
||||
// a 'for' loop
|
||||
type asyncLoopLogger struct {
|
||||
asyncLogger
|
||||
}
|
||||
|
||||
// NewAsyncLoopLogger creates a new asynchronous loop logger
|
||||
func NewAsyncLoopLogger(config *logConfig) *asyncLoopLogger {
|
||||
|
||||
asnLoopLogger := new(asyncLoopLogger)
|
||||
|
||||
asnLoopLogger.asyncLogger = *newAsyncLogger(config)
|
||||
|
||||
go asnLoopLogger.processQueue()
|
||||
|
||||
return asnLoopLogger
|
||||
}
|
||||
|
||||
func (asnLoopLogger *asyncLoopLogger) processItem() (closed bool) {
|
||||
asnLoopLogger.queueHasElements.L.Lock()
|
||||
defer asnLoopLogger.queueHasElements.L.Unlock()
|
||||
|
||||
for asnLoopLogger.msgQueue.Len() == 0 && !asnLoopLogger.Closed() {
|
||||
asnLoopLogger.queueHasElements.Wait()
|
||||
}
|
||||
|
||||
if asnLoopLogger.Closed() {
|
||||
return true
|
||||
}
|
||||
|
||||
asnLoopLogger.processQueueElement()
|
||||
return false
|
||||
}
|
||||
|
||||
func (asnLoopLogger *asyncLoopLogger) processQueue() {
|
||||
for !asnLoopLogger.Closed() {
|
||||
closed := asnLoopLogger.processItem()
|
||||
|
||||
if closed {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||
// list of conditions and the following disclaimer.
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package seelog
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
// asyncTimerLogger represents asynchronous logger which processes the log queue each
|
||||
// 'duration' nanoseconds
|
||||
type asyncTimerLogger struct {
|
||||
asyncLogger
|
||||
interval time.Duration
|
||||
}
|
||||
|
||||
// NewAsyncLoopLogger creates a new asynchronous loop logger
|
||||
func NewAsyncTimerLogger(config *logConfig, interval time.Duration) (*asyncTimerLogger, error) {
|
||||
|
||||
if interval <= 0 {
|
||||
return nil, errors.New("async logger interval should be > 0")
|
||||
}
|
||||
|
||||
asnTimerLogger := new(asyncTimerLogger)
|
||||
|
||||
asnTimerLogger.asyncLogger = *newAsyncLogger(config)
|
||||
asnTimerLogger.interval = interval
|
||||
|
||||
go asnTimerLogger.processQueue()
|
||||
|
||||
return asnTimerLogger, nil
|
||||
}
|
||||
|
||||
func (asnTimerLogger *asyncTimerLogger) processItem() (closed bool) {
|
||||
asnTimerLogger.queueHasElements.L.Lock()
|
||||
defer asnTimerLogger.queueHasElements.L.Unlock()
|
||||
|
||||
for asnTimerLogger.msgQueue.Len() == 0 && !asnTimerLogger.Closed() {
|
||||
asnTimerLogger.queueHasElements.Wait()
|
||||
}
|
||||
|
||||
if asnTimerLogger.Closed() {
|
||||
return true
|
||||
}
|
||||
|
||||
asnTimerLogger.processQueueElement()
|
||||
return false
|
||||
}
|
||||
|
||||
func (asnTimerLogger *asyncTimerLogger) processQueue() {
|
||||
for !asnTimerLogger.Closed() {
|
||||
closed := asnTimerLogger.processItem()
|
||||
|
||||
if closed {
|
||||
break
|
||||
}
|
||||
|
||||
<-time.After(asnTimerLogger.interval)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||
// list of conditions and the following disclaimer.
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package seelog
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// syncLogger performs logging in the same goroutine where 'Trace/Debug/...'
|
||||
// func was called
|
||||
type syncLogger struct {
|
||||
commonLogger
|
||||
}
|
||||
|
||||
// NewSyncLogger creates a new synchronous logger
|
||||
func NewSyncLogger(config *logConfig) *syncLogger {
|
||||
syncLogger := new(syncLogger)
|
||||
|
||||
syncLogger.commonLogger = *newCommonLogger(config, syncLogger)
|
||||
|
||||
return syncLogger
|
||||
}
|
||||
|
||||
func (syncLogger *syncLogger) innerLog(
|
||||
level LogLevel,
|
||||
context LogContextInterface,
|
||||
message fmt.Stringer) {
|
||||
|
||||
syncLogger.processLogMsg(level, message, context)
|
||||
}
|
||||
|
||||
func (syncLogger *syncLogger) Close() {
|
||||
syncLogger.m.Lock()
|
||||
defer syncLogger.m.Unlock()
|
||||
|
||||
if !syncLogger.Closed() {
|
||||
if err := syncLogger.config.RootDispatcher.Close(); err != nil {
|
||||
reportInternalError(err)
|
||||
}
|
||||
syncLogger.closedM.Lock()
|
||||
syncLogger.closed = true
|
||||
syncLogger.closedM.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func (syncLogger *syncLogger) Flush() {
|
||||
syncLogger.m.Lock()
|
||||
defer syncLogger.m.Unlock()
|
||||
|
||||
if !syncLogger.Closed() {
|
||||
syncLogger.config.RootDispatcher.Flush()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,188 @@
|
|||
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||
// list of conditions and the following disclaimer.
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package seelog
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
// LoggerFromConfigAsFile creates logger with config from file. File should contain valid seelog xml.
|
||||
func LoggerFromConfigAsFile(fileName string) (LoggerInterface, error) {
|
||||
file, err := os.Open(fileName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
conf, err := configFromReader(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return createLoggerFromFullConfig(conf)
|
||||
}
|
||||
|
||||
// LoggerFromConfigAsBytes creates a logger with config from bytes stream. Bytes should contain valid seelog xml.
|
||||
func LoggerFromConfigAsBytes(data []byte) (LoggerInterface, error) {
|
||||
conf, err := configFromReader(bytes.NewBuffer(data))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return createLoggerFromFullConfig(conf)
|
||||
}
|
||||
|
||||
// LoggerFromConfigAsString creates a logger with config from a string. String should contain valid seelog xml.
|
||||
func LoggerFromConfigAsString(data string) (LoggerInterface, error) {
|
||||
return LoggerFromConfigAsBytes([]byte(data))
|
||||
}
|
||||
|
||||
// LoggerFromParamConfigAsFile does the same as LoggerFromConfigAsFile, but includes special parser options.
|
||||
// See 'CfgParseParams' comments.
|
||||
func LoggerFromParamConfigAsFile(fileName string, parserParams *CfgParseParams) (LoggerInterface, error) {
|
||||
file, err := os.Open(fileName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
conf, err := configFromReaderWithConfig(file, parserParams)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return createLoggerFromFullConfig(conf)
|
||||
}
|
||||
|
||||
// LoggerFromParamConfigAsBytes does the same as LoggerFromConfigAsBytes, but includes special parser options.
|
||||
// See 'CfgParseParams' comments.
|
||||
func LoggerFromParamConfigAsBytes(data []byte, parserParams *CfgParseParams) (LoggerInterface, error) {
|
||||
conf, err := configFromReaderWithConfig(bytes.NewBuffer(data), parserParams)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return createLoggerFromFullConfig(conf)
|
||||
}
|
||||
|
||||
// LoggerFromParamConfigAsString does the same as LoggerFromConfigAsString, but includes special parser options.
|
||||
// See 'CfgParseParams' comments.
|
||||
func LoggerFromParamConfigAsString(data string, parserParams *CfgParseParams) (LoggerInterface, error) {
|
||||
return LoggerFromParamConfigAsBytes([]byte(data), parserParams)
|
||||
}
|
||||
|
||||
// LoggerFromWriterWithMinLevel is shortcut for LoggerFromWriterWithMinLevelAndFormat(output, minLevel, DefaultMsgFormat)
|
||||
func LoggerFromWriterWithMinLevel(output io.Writer, minLevel LogLevel) (LoggerInterface, error) {
|
||||
return LoggerFromWriterWithMinLevelAndFormat(output, minLevel, DefaultMsgFormat)
|
||||
}
|
||||
|
||||
// LoggerFromWriterWithMinLevelAndFormat creates a proxy logger that uses io.Writer as the
|
||||
// receiver with minimal level = minLevel and with specified format.
|
||||
//
|
||||
// All messages with level more or equal to minLevel will be written to output and
|
||||
// formatted using the default seelog format.
|
||||
//
|
||||
// Can be called for usage with non-Seelog systems
|
||||
func LoggerFromWriterWithMinLevelAndFormat(output io.Writer, minLevel LogLevel, format string) (LoggerInterface, error) {
|
||||
constraints, err := NewMinMaxConstraints(minLevel, CriticalLvl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
formatter, err := NewFormatter(format)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dispatcher, err := NewSplitDispatcher(formatter, []interface{}{output})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conf, err := newFullLoggerConfig(constraints, make([]*LogLevelException, 0), dispatcher, syncloggerTypeFromString, nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return createLoggerFromFullConfig(conf)
|
||||
}
|
||||
|
||||
// LoggerFromXMLDecoder creates logger with config from a XML decoder starting from a specific node.
|
||||
// It should contain valid seelog xml, except for root node name.
|
||||
func LoggerFromXMLDecoder(xmlParser *xml.Decoder, rootNode xml.Token) (LoggerInterface, error) {
|
||||
conf, err := configFromXMLDecoder(xmlParser, rootNode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return createLoggerFromFullConfig(conf)
|
||||
}
|
||||
|
||||
// LoggerFromCustomReceiver creates a proxy logger that uses a CustomReceiver as the
|
||||
// receiver.
|
||||
//
|
||||
// All messages will be sent to the specified custom receiver without additional
|
||||
// formatting ('%Msg' format is used).
|
||||
//
|
||||
// Check CustomReceiver, RegisterReceiver for additional info.
|
||||
//
|
||||
// NOTE 1: CustomReceiver.AfterParse is only called when a receiver is instantiated
|
||||
// by the config parser while parsing config. So, if you are not planning to use the
|
||||
// same CustomReceiver for both proxying (via LoggerFromCustomReceiver call) and
|
||||
// loading from config, just leave AfterParse implementation empty.
|
||||
//
|
||||
// NOTE 2: Unlike RegisterReceiver, LoggerFromCustomReceiver takes an already initialized
|
||||
// instance that implements CustomReceiver. So, fill it with data and perform any initialization
|
||||
// logic before calling this func and it won't be lost.
|
||||
//
|
||||
// So:
|
||||
// * RegisterReceiver takes value just to get the reflect.Type from it and then
|
||||
// instantiate it as many times as config is reloaded.
|
||||
//
|
||||
// * LoggerFromCustomReceiver takes value and uses it without modification and
|
||||
// reinstantiation, directy passing it to the dispatcher tree.
|
||||
func LoggerFromCustomReceiver(receiver CustomReceiver) (LoggerInterface, error) {
|
||||
constraints, err := NewMinMaxConstraints(TraceLvl, CriticalLvl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
output, err := NewCustomReceiverDispatcherByValue(msgonlyformatter, receiver, "user-proxy", CustomReceiverInitArgs{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dispatcher, err := NewSplitDispatcher(msgonlyformatter, []interface{}{output})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conf, err := newFullLoggerConfig(constraints, make([]*LogLevelException, 0), dispatcher, syncloggerTypeFromString, nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return createLoggerFromFullConfig(conf)
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||
// list of conditions and the following disclaimer.
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package seelog
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
var (
|
||||
errNodeMustHaveChildren = errors.New("node must have children")
|
||||
errNodeCannotHaveChildren = errors.New("node cannot have children")
|
||||
)
|
||||
|
||||
type unexpectedChildElementError struct {
|
||||
baseError
|
||||
}
|
||||
|
||||
func newUnexpectedChildElementError(msg string) *unexpectedChildElementError {
|
||||
custmsg := "Unexpected child element: " + msg
|
||||
return &unexpectedChildElementError{baseError{message: custmsg}}
|
||||
}
|
||||
|
||||
type missingArgumentError struct {
|
||||
baseError
|
||||
}
|
||||
|
||||
func newMissingArgumentError(nodeName, attrName string) *missingArgumentError {
|
||||
custmsg := "Output '" + nodeName + "' has no '" + attrName + "' attribute"
|
||||
return &missingArgumentError{baseError{message: custmsg}}
|
||||
}
|
||||
|
||||
type unexpectedAttributeError struct {
|
||||
baseError
|
||||
}
|
||||
|
||||
func newUnexpectedAttributeError(nodeName, attr string) *unexpectedAttributeError {
|
||||
custmsg := nodeName + " has unexpected attribute: " + attr
|
||||
return &unexpectedAttributeError{baseError{message: custmsg}}
|
||||
}
|
|
@ -0,0 +1,141 @@
|
|||
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||
// list of conditions and the following disclaimer.
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package seelog
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
type loggerTypeFromString uint8
|
||||
|
||||
const (
|
||||
syncloggerTypeFromString = iota
|
||||
asyncLooploggerTypeFromString
|
||||
asyncTimerloggerTypeFromString
|
||||
adaptiveLoggerTypeFromString
|
||||
defaultloggerTypeFromString = asyncLooploggerTypeFromString
|
||||
)
|
||||
|
||||
const (
|
||||
syncloggerTypeFromStringStr = "sync"
|
||||
asyncloggerTypeFromStringStr = "asyncloop"
|
||||
asyncTimerloggerTypeFromStringStr = "asynctimer"
|
||||
adaptiveLoggerTypeFromStringStr = "adaptive"
|
||||
)
|
||||
|
||||
// asyncTimerLoggerData represents specific data for async timer logger
|
||||
type asyncTimerLoggerData struct {
|
||||
AsyncInterval uint32
|
||||
}
|
||||
|
||||
// adaptiveLoggerData represents specific data for adaptive timer logger
|
||||
type adaptiveLoggerData struct {
|
||||
MinInterval uint32
|
||||
MaxInterval uint32
|
||||
CriticalMsgCount uint32
|
||||
}
|
||||
|
||||
var loggerTypeToStringRepresentations = map[loggerTypeFromString]string{
|
||||
syncloggerTypeFromString: syncloggerTypeFromStringStr,
|
||||
asyncLooploggerTypeFromString: asyncloggerTypeFromStringStr,
|
||||
asyncTimerloggerTypeFromString: asyncTimerloggerTypeFromStringStr,
|
||||
adaptiveLoggerTypeFromString: adaptiveLoggerTypeFromStringStr,
|
||||
}
|
||||
|
||||
// getLoggerTypeFromString parses a string and returns a corresponding logger type, if successful.
|
||||
func getLoggerTypeFromString(logTypeString string) (level loggerTypeFromString, found bool) {
|
||||
for logType, logTypeStr := range loggerTypeToStringRepresentations {
|
||||
if logTypeStr == logTypeString {
|
||||
return logType, true
|
||||
}
|
||||
}
|
||||
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// logConfig stores logging configuration. Contains messages dispatcher, allowed log level rules
|
||||
// (general constraints and exceptions)
|
||||
type logConfig struct {
|
||||
Constraints logLevelConstraints // General log level rules (>min and <max, or set of allowed levels)
|
||||
Exceptions []*LogLevelException // Exceptions to general rules for specific files or funcs
|
||||
RootDispatcher dispatcherInterface // Root of output tree
|
||||
}
|
||||
|
||||
func NewLoggerConfig(c logLevelConstraints, e []*LogLevelException, d dispatcherInterface) *logConfig {
|
||||
return &logConfig{c, e, d}
|
||||
}
|
||||
|
||||
// configForParsing is used when parsing config from file: logger type is deduced from string, params
|
||||
// need to be converted from attributes to values and passed to specific logger constructor. Also,
|
||||
// custom registered receivers and other parse params are used in this case.
|
||||
type configForParsing struct {
|
||||
logConfig
|
||||
LogType loggerTypeFromString
|
||||
LoggerData interface{}
|
||||
Params *CfgParseParams // Check cfg_parser: CfgParseParams
|
||||
}
|
||||
|
||||
func newFullLoggerConfig(
|
||||
constraints logLevelConstraints,
|
||||
exceptions []*LogLevelException,
|
||||
rootDispatcher dispatcherInterface,
|
||||
logType loggerTypeFromString,
|
||||
logData interface{},
|
||||
cfgParams *CfgParseParams) (*configForParsing, error) {
|
||||
if constraints == nil {
|
||||
return nil, errors.New("constraints can not be nil")
|
||||
}
|
||||
if rootDispatcher == nil {
|
||||
return nil, errors.New("rootDispatcher can not be nil")
|
||||
}
|
||||
|
||||
config := new(configForParsing)
|
||||
config.Constraints = constraints
|
||||
config.Exceptions = exceptions
|
||||
config.RootDispatcher = rootDispatcher
|
||||
config.LogType = logType
|
||||
config.LoggerData = logData
|
||||
config.Params = cfgParams
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// IsAllowed returns true if logging with specified log level is allowed in current context.
|
||||
// If any of exception patterns match current context, then exception constraints are applied. Otherwise,
|
||||
// the general constraints are used.
|
||||
func (config *logConfig) IsAllowed(level LogLevel, context LogContextInterface) bool {
|
||||
allowed := config.Constraints.IsAllowed(level) // General rule
|
||||
|
||||
// Exceptions:
|
||||
if context.IsValid() {
|
||||
for _, exception := range config.Exceptions {
|
||||
if exception.MatchesContext(context) {
|
||||
return exception.IsAllowed(level)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return allowed
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,25 @@
|
|||
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||
// list of conditions and the following disclaimer.
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package seelog
|
|
@ -0,0 +1,162 @@
|
|||
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||
// list of conditions and the following disclaimer.
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package seelog
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Represents constraints which form a general rule for log levels selection
|
||||
type logLevelConstraints interface {
|
||||
IsAllowed(level LogLevel) bool
|
||||
}
|
||||
|
||||
// A minMaxConstraints represents constraints which use minimal and maximal allowed log levels.
|
||||
type minMaxConstraints struct {
|
||||
min LogLevel
|
||||
max LogLevel
|
||||
}
|
||||
|
||||
// NewMinMaxConstraints creates a new minMaxConstraints struct with the specified min and max levels.
|
||||
func NewMinMaxConstraints(min LogLevel, max LogLevel) (*minMaxConstraints, error) {
|
||||
if min > max {
|
||||
return nil, fmt.Errorf("min level can't be greater than max. Got min: %d, max: %d", min, max)
|
||||
}
|
||||
if min < TraceLvl || min > CriticalLvl {
|
||||
return nil, fmt.Errorf("min level can't be less than Trace or greater than Critical. Got min: %d", min)
|
||||
}
|
||||
if max < TraceLvl || max > CriticalLvl {
|
||||
return nil, fmt.Errorf("max level can't be less than Trace or greater than Critical. Got max: %d", max)
|
||||
}
|
||||
|
||||
return &minMaxConstraints{min, max}, nil
|
||||
}
|
||||
|
||||
// IsAllowed returns true, if log level is in [min, max] range (inclusive).
|
||||
func (minMaxConstr *minMaxConstraints) IsAllowed(level LogLevel) bool {
|
||||
return level >= minMaxConstr.min && level <= minMaxConstr.max
|
||||
}
|
||||
|
||||
func (minMaxConstr *minMaxConstraints) String() string {
|
||||
return fmt.Sprintf("Min: %s. Max: %s", minMaxConstr.min, minMaxConstr.max)
|
||||
}
|
||||
|
||||
//=======================================================
|
||||
|
||||
// A listConstraints represents constraints which use allowed log levels list.
|
||||
type listConstraints struct {
|
||||
allowedLevels map[LogLevel]bool
|
||||
}
|
||||
|
||||
// NewListConstraints creates a new listConstraints struct with the specified allowed levels.
|
||||
func NewListConstraints(allowList []LogLevel) (*listConstraints, error) {
|
||||
if allowList == nil {
|
||||
return nil, errors.New("list can't be nil")
|
||||
}
|
||||
|
||||
allowLevels, err := createMapFromList(allowList)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = validateOffLevel(allowLevels)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &listConstraints{allowLevels}, nil
|
||||
}
|
||||
|
||||
func (listConstr *listConstraints) String() string {
|
||||
allowedList := "List: "
|
||||
|
||||
listLevel := make([]string, len(listConstr.allowedLevels))
|
||||
|
||||
var logLevel LogLevel
|
||||
i := 0
|
||||
for logLevel = TraceLvl; logLevel <= Off; logLevel++ {
|
||||
if listConstr.allowedLevels[logLevel] {
|
||||
listLevel[i] = logLevel.String()
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
allowedList += strings.Join(listLevel, ",")
|
||||
|
||||
return allowedList
|
||||
}
|
||||
|
||||
func createMapFromList(allowedList []LogLevel) (map[LogLevel]bool, error) {
|
||||
allowedLevels := make(map[LogLevel]bool, 0)
|
||||
for _, level := range allowedList {
|
||||
if level < TraceLvl || level > Off {
|
||||
return nil, fmt.Errorf("level can't be less than Trace or greater than Critical. Got level: %d", level)
|
||||
}
|
||||
allowedLevels[level] = true
|
||||
}
|
||||
return allowedLevels, nil
|
||||
}
|
||||
func validateOffLevel(allowedLevels map[LogLevel]bool) error {
|
||||
if _, ok := allowedLevels[Off]; ok && len(allowedLevels) > 1 {
|
||||
return errors.New("logLevel Off cant be mixed with other levels")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsAllowed returns true, if log level is in allowed log levels list.
|
||||
// If the list contains the only item 'common.Off' then IsAllowed will always return false for any input values.
|
||||
func (listConstr *listConstraints) IsAllowed(level LogLevel) bool {
|
||||
for l := range listConstr.allowedLevels {
|
||||
if l == level && level != Off {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// AllowedLevels returns allowed levels configuration as a map.
|
||||
func (listConstr *listConstraints) AllowedLevels() map[LogLevel]bool {
|
||||
return listConstr.allowedLevels
|
||||
}
|
||||
|
||||
//=======================================================
|
||||
|
||||
type offConstraints struct {
|
||||
}
|
||||
|
||||
func NewOffConstraints() (*offConstraints, error) {
|
||||
return &offConstraints{}, nil
|
||||
}
|
||||
|
||||
func (offConstr *offConstraints) IsAllowed(level LogLevel) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (offConstr *offConstraints) String() string {
|
||||
return "Off constraint"
|
||||
}
|
|
@ -0,0 +1,194 @@
|
|||
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||
// list of conditions and the following disclaimer.
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package seelog
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var workingDir = "/"
|
||||
|
||||
func init() {
|
||||
wd, err := os.Getwd()
|
||||
if err == nil {
|
||||
workingDir = filepath.ToSlash(wd) + "/"
|
||||
}
|
||||
}
|
||||
|
||||
// Represents runtime caller context.
|
||||
type LogContextInterface interface {
|
||||
// Caller's function name.
|
||||
Func() string
|
||||
// Caller's line number.
|
||||
Line() int
|
||||
// Caller's file short path (in slashed form).
|
||||
ShortPath() string
|
||||
// Caller's file full path (in slashed form).
|
||||
FullPath() string
|
||||
// Caller's file name (without path).
|
||||
FileName() string
|
||||
// True if the context is correct and may be used.
|
||||
// If false, then an error in context evaluation occurred and
|
||||
// all its other data may be corrupted.
|
||||
IsValid() bool
|
||||
// Time when log function was called.
|
||||
CallTime() time.Time
|
||||
// Custom context that can be set by calling logger.SetContext
|
||||
CustomContext() interface{}
|
||||
}
|
||||
|
||||
// Returns context of the caller
|
||||
func currentContext(custom interface{}) (LogContextInterface, error) {
|
||||
return specifyContext(1, custom)
|
||||
}
|
||||
|
||||
func extractCallerInfo(skip int) (fullPath string, shortPath string, funcName string, line int, err error) {
|
||||
pc, fp, ln, ok := runtime.Caller(skip)
|
||||
if !ok {
|
||||
err = fmt.Errorf("error during runtime.Caller")
|
||||
return
|
||||
}
|
||||
line = ln
|
||||
fullPath = fp
|
||||
if strings.HasPrefix(fp, workingDir) {
|
||||
shortPath = fp[len(workingDir):]
|
||||
} else {
|
||||
shortPath = fp
|
||||
}
|
||||
funcName = runtime.FuncForPC(pc).Name()
|
||||
if strings.HasPrefix(funcName, workingDir) {
|
||||
funcName = funcName[len(workingDir):]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Returns context of the function with placed "skip" stack frames of the caller
|
||||
// If skip == 0 then behaves like currentContext
|
||||
// Context is returned in any situation, even if error occurs. But, if an error
|
||||
// occurs, the returned context is an error context, which contains no paths
|
||||
// or names, but states that they can't be extracted.
|
||||
func specifyContext(skip int, custom interface{}) (LogContextInterface, error) {
|
||||
callTime := time.Now()
|
||||
if skip < 0 {
|
||||
err := fmt.Errorf("can not skip negative stack frames")
|
||||
return &errorContext{callTime, err}, err
|
||||
}
|
||||
fullPath, shortPath, funcName, line, err := extractCallerInfo(skip + 2)
|
||||
if err != nil {
|
||||
return &errorContext{callTime, err}, err
|
||||
}
|
||||
_, fileName := filepath.Split(fullPath)
|
||||
return &logContext{funcName, line, shortPath, fullPath, fileName, callTime, custom}, nil
|
||||
}
|
||||
|
||||
// Represents a normal runtime caller context.
|
||||
type logContext struct {
|
||||
funcName string
|
||||
line int
|
||||
shortPath string
|
||||
fullPath string
|
||||
fileName string
|
||||
callTime time.Time
|
||||
custom interface{}
|
||||
}
|
||||
|
||||
func (context *logContext) IsValid() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (context *logContext) Func() string {
|
||||
return context.funcName
|
||||
}
|
||||
|
||||
func (context *logContext) Line() int {
|
||||
return context.line
|
||||
}
|
||||
|
||||
func (context *logContext) ShortPath() string {
|
||||
return context.shortPath
|
||||
}
|
||||
|
||||
func (context *logContext) FullPath() string {
|
||||
return context.fullPath
|
||||
}
|
||||
|
||||
func (context *logContext) FileName() string {
|
||||
return context.fileName
|
||||
}
|
||||
|
||||
func (context *logContext) CallTime() time.Time {
|
||||
return context.callTime
|
||||
}
|
||||
|
||||
func (context *logContext) CustomContext() interface{} {
|
||||
return context.custom
|
||||
}
|
||||
|
||||
// Represents an error context
|
||||
type errorContext struct {
|
||||
errorTime time.Time
|
||||
err error
|
||||
}
|
||||
|
||||
func (errContext *errorContext) getErrorText(prefix string) string {
|
||||
return fmt.Sprintf("%s() error: %s", prefix, errContext.err)
|
||||
}
|
||||
|
||||
func (errContext *errorContext) IsValid() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (errContext *errorContext) Line() int {
|
||||
return -1
|
||||
}
|
||||
|
||||
func (errContext *errorContext) Func() string {
|
||||
return errContext.getErrorText("Func")
|
||||
}
|
||||
|
||||
func (errContext *errorContext) ShortPath() string {
|
||||
return errContext.getErrorText("ShortPath")
|
||||
}
|
||||
|
||||
func (errContext *errorContext) FullPath() string {
|
||||
return errContext.getErrorText("FullPath")
|
||||
}
|
||||
|
||||
func (errContext *errorContext) FileName() string {
|
||||
return errContext.getErrorText("FileName")
|
||||
}
|
||||
|
||||
func (errContext *errorContext) CallTime() time.Time {
|
||||
return errContext.errorTime
|
||||
}
|
||||
|
||||
func (errContext *errorContext) CustomContext() interface{} {
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,194 @@
|
|||
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||
// list of conditions and the following disclaimer.
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package seelog
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Used in rules creation to validate input file and func filters
|
||||
var (
|
||||
fileFormatValidator = regexp.MustCompile(`[a-zA-Z0-9\\/ _\*\.]*`)
|
||||
funcFormatValidator = regexp.MustCompile(`[a-zA-Z0-9_\*\.]*`)
|
||||
)
|
||||
|
||||
// LogLevelException represents an exceptional case used when you need some specific files or funcs to
|
||||
// override general constraints and to use their own.
|
||||
type LogLevelException struct {
|
||||
funcPatternParts []string
|
||||
filePatternParts []string
|
||||
|
||||
funcPattern string
|
||||
filePattern string
|
||||
|
||||
constraints logLevelConstraints
|
||||
}
|
||||
|
||||
// NewLogLevelException creates a new exception.
|
||||
func NewLogLevelException(funcPattern string, filePattern string, constraints logLevelConstraints) (*LogLevelException, error) {
|
||||
if constraints == nil {
|
||||
return nil, errors.New("constraints can not be nil")
|
||||
}
|
||||
|
||||
exception := new(LogLevelException)
|
||||
|
||||
err := exception.initFuncPatternParts(funcPattern)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
exception.funcPattern = strings.Join(exception.funcPatternParts, "")
|
||||
|
||||
err = exception.initFilePatternParts(filePattern)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
exception.filePattern = strings.Join(exception.filePatternParts, "")
|
||||
|
||||
exception.constraints = constraints
|
||||
|
||||
return exception, nil
|
||||
}
|
||||
|
||||
// MatchesContext returns true if context matches the patterns of this LogLevelException
|
||||
func (logLevelEx *LogLevelException) MatchesContext(context LogContextInterface) bool {
|
||||
return logLevelEx.match(context.Func(), context.FullPath())
|
||||
}
|
||||
|
||||
// IsAllowed returns true if log level is allowed according to the constraints of this LogLevelException
|
||||
func (logLevelEx *LogLevelException) IsAllowed(level LogLevel) bool {
|
||||
return logLevelEx.constraints.IsAllowed(level)
|
||||
}
|
||||
|
||||
// FuncPattern returns the function pattern of a exception
|
||||
func (logLevelEx *LogLevelException) FuncPattern() string {
|
||||
return logLevelEx.funcPattern
|
||||
}
|
||||
|
||||
// FuncPattern returns the file pattern of a exception
|
||||
func (logLevelEx *LogLevelException) FilePattern() string {
|
||||
return logLevelEx.filePattern
|
||||
}
|
||||
|
||||
// initFuncPatternParts checks whether the func filter has a correct format and splits funcPattern on parts
|
||||
func (logLevelEx *LogLevelException) initFuncPatternParts(funcPattern string) (err error) {
|
||||
|
||||
if funcFormatValidator.FindString(funcPattern) != funcPattern {
|
||||
return errors.New("func path \"" + funcPattern + "\" contains incorrect symbols. Only a-z A-Z 0-9 _ * . allowed)")
|
||||
}
|
||||
|
||||
logLevelEx.funcPatternParts = splitPattern(funcPattern)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Checks whether the file filter has a correct format and splits file patterns using splitPattern.
|
||||
func (logLevelEx *LogLevelException) initFilePatternParts(filePattern string) (err error) {
|
||||
|
||||
if fileFormatValidator.FindString(filePattern) != filePattern {
|
||||
return errors.New("file path \"" + filePattern + "\" contains incorrect symbols. Only a-z A-Z 0-9 \\ / _ * . allowed)")
|
||||
}
|
||||
|
||||
logLevelEx.filePatternParts = splitPattern(filePattern)
|
||||
return err
|
||||
}
|
||||
|
||||
func (logLevelEx *LogLevelException) match(funcPath string, filePath string) bool {
|
||||
if !stringMatchesPattern(logLevelEx.funcPatternParts, funcPath) {
|
||||
return false
|
||||
}
|
||||
return stringMatchesPattern(logLevelEx.filePatternParts, filePath)
|
||||
}
|
||||
|
||||
func (logLevelEx *LogLevelException) String() string {
|
||||
str := fmt.Sprintf("Func: %s File: %s", logLevelEx.funcPattern, logLevelEx.filePattern)
|
||||
|
||||
if logLevelEx.constraints != nil {
|
||||
str += fmt.Sprintf("Constr: %s", logLevelEx.constraints)
|
||||
} else {
|
||||
str += "nil"
|
||||
}
|
||||
|
||||
return str
|
||||
}
|
||||
|
||||
// splitPattern splits pattern into strings and asterisks. Example: "ab*cde**f" -> ["ab", "*", "cde", "*", "f"]
|
||||
func splitPattern(pattern string) []string {
|
||||
var patternParts []string
|
||||
var lastChar rune
|
||||
for _, char := range pattern {
|
||||
if char == '*' {
|
||||
if lastChar != '*' {
|
||||
patternParts = append(patternParts, "*")
|
||||
}
|
||||
} else {
|
||||
if len(patternParts) != 0 && lastChar != '*' {
|
||||
patternParts[len(patternParts)-1] += string(char)
|
||||
} else {
|
||||
patternParts = append(patternParts, string(char))
|
||||
}
|
||||
}
|
||||
lastChar = char
|
||||
}
|
||||
|
||||
return patternParts
|
||||
}
|
||||
|
||||
// stringMatchesPattern check whether testString matches pattern with asterisks.
|
||||
// Standard regexp functionality is not used here because of performance issues.
|
||||
func stringMatchesPattern(patternparts []string, testString string) bool {
|
||||
if len(patternparts) == 0 {
|
||||
return len(testString) == 0
|
||||
}
|
||||
|
||||
part := patternparts[0]
|
||||
if part != "*" {
|
||||
index := strings.Index(testString, part)
|
||||
if index == 0 {
|
||||
return stringMatchesPattern(patternparts[1:], testString[len(part):])
|
||||
}
|
||||
} else {
|
||||
if len(patternparts) == 1 {
|
||||
return true
|
||||
}
|
||||
|
||||
newTestString := testString
|
||||
part = patternparts[1]
|
||||
for {
|
||||
index := strings.Index(newTestString, part)
|
||||
if index == -1 {
|
||||
break
|
||||
}
|
||||
|
||||
newTestString = newTestString[index+len(part):]
|
||||
result := stringMatchesPattern(patternparts[2:], newTestString)
|
||||
if result {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||
// list of conditions and the following disclaimer.
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package seelog
|
||||
|
||||
// flusherInterface represents all objects that have to do cleanup
|
||||
// at certain moments of time (e.g. before app shutdown to avoid data loss)
|
||||
type flusherInterface interface {
|
||||
Flush()
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||
// list of conditions and the following disclaimer.
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package seelog
|
||||
|
||||
// Log level type
|
||||
type LogLevel uint8
|
||||
|
||||
// Log levels
|
||||
const (
|
||||
TraceLvl = iota
|
||||
DebugLvl
|
||||
InfoLvl
|
||||
WarnLvl
|
||||
ErrorLvl
|
||||
CriticalLvl
|
||||
Off
|
||||
)
|
||||
|
||||
// Log level string representations (used in configuration files)
|
||||
const (
|
||||
TraceStr = "trace"
|
||||
DebugStr = "debug"
|
||||
InfoStr = "info"
|
||||
WarnStr = "warn"
|
||||
ErrorStr = "error"
|
||||
CriticalStr = "critical"
|
||||
OffStr = "off"
|
||||
)
|
||||
|
||||
var levelToStringRepresentations = map[LogLevel]string{
|
||||
TraceLvl: TraceStr,
|
||||
DebugLvl: DebugStr,
|
||||
InfoLvl: InfoStr,
|
||||
WarnLvl: WarnStr,
|
||||
ErrorLvl: ErrorStr,
|
||||
CriticalLvl: CriticalStr,
|
||||
Off: OffStr,
|
||||
}
|
||||
|
||||
// LogLevelFromString parses a string and returns a corresponding log level, if sucessfull.
|
||||
func LogLevelFromString(levelStr string) (level LogLevel, found bool) {
|
||||
for lvl, lvlStr := range levelToStringRepresentations {
|
||||
if lvlStr == levelStr {
|
||||
return lvl, true
|
||||
}
|
||||
}
|
||||
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// LogLevelToString returns seelog string representation for a specified level. Returns "" for invalid log levels.
|
||||
func (level LogLevel) String() string {
|
||||
levelStr, ok := levelToStringRepresentations[level]
|
||||
if ok {
|
||||
return levelStr
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
|
@ -0,0 +1,242 @@
|
|||
// Copyright (c) 2013 - Cloud Instruments Co., Ltd.
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||
// list of conditions and the following disclaimer.
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package seelog
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
)
|
||||
|
||||
var registeredReceivers = make(map[string]reflect.Type)
|
||||
|
||||
// RegisterReceiver records a custom receiver type, identified by a value
|
||||
// of that type (second argument), under the specified name. Registered
|
||||
// names can be used in the "name" attribute of <custom> config items.
|
||||
//
|
||||
// RegisterReceiver takes the type of the receiver argument, without taking
|
||||
// the value into the account. So do NOT enter any data to the second argument
|
||||
// and only call it like:
|
||||
// RegisterReceiver("somename", &MyReceiverType{})
|
||||
//
|
||||
// After that, when a '<custom>' config tag with this name is used,
|
||||
// a receiver of the specified type would be instantiated. Check
|
||||
// CustomReceiver comments for interface details.
|
||||
//
|
||||
// NOTE 1: RegisterReceiver fails if you attempt to register different types
|
||||
// with the same name.
|
||||
//
|
||||
// NOTE 2: RegisterReceiver registers those receivers that must be used in
|
||||
// the configuration files (<custom> items). Basically it is just the way
|
||||
// you tell seelog config parser what should it do when it meets a
|
||||
// <custom> tag with a specific name and data attributes.
|
||||
//
|
||||
// But If you are only using seelog as a proxy to an already instantiated
|
||||
// CustomReceiver (via LoggerFromCustomReceiver func), you should not call RegisterReceiver.
|
||||
func RegisterReceiver(name string, receiver CustomReceiver) {
|
||||
newType := reflect.TypeOf(reflect.ValueOf(receiver).Elem().Interface())
|
||||
if t, ok := registeredReceivers[name]; ok && t != newType {
|
||||
panic(fmt.Sprintf("duplicate types for %s: %s != %s", name, t, newType))
|
||||
}
|
||||
registeredReceivers[name] = newType
|
||||
}
|
||||
|
||||
func customReceiverByName(name string) (creceiver CustomReceiver, err error) {
|
||||
rt, ok := registeredReceivers[name]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("custom receiver name not registered: '%s'", name)
|
||||
}
|
||||
v, ok := reflect.New(rt).Interface().(CustomReceiver)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("cannot instantiate receiver with name='%s'", name)
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// CustomReceiverInitArgs represent arguments passed to the CustomReceiver.Init
|
||||
// func when custom receiver is being initialized.
|
||||
type CustomReceiverInitArgs struct {
|
||||
// XmlCustomAttrs represent '<custom>' xml config item attributes that
|
||||
// start with "data-". Map keys will be the attribute names without the "data-".
|
||||
// Map values will the those attribute values.
|
||||
//
|
||||
// E.g. if you have a '<custom name="somename" data-attr1="a1" data-attr2="a2"/>'
|
||||
// you will get map with 2 key-value pairs: "attr1"->"a1", "attr2"->"a2"
|
||||
//
|
||||
// Note that in custom items you can only use allowed attributes, like "name" and
|
||||
// your custom attributes, starting with "data-". Any other will lead to a
|
||||
// parsing error.
|
||||
XmlCustomAttrs map[string]string
|
||||
}
|
||||
|
||||
// CustomReceiver is the interface that external custom seelog message receivers
|
||||
// must implement in order to be able to process seelog messages. Those receivers
|
||||
// are set in the xml config file using the <custom> tag. Check receivers reference
|
||||
// wiki section on that.
|
||||
//
|
||||
// Use seelog.RegisterReceiver on the receiver type before using it.
|
||||
type CustomReceiver interface {
|
||||
// ReceiveMessage is called when the custom receiver gets seelog message from
|
||||
// a parent dispatcher.
|
||||
//
|
||||
// Message, level and context args represent all data that was included in the seelog
|
||||
// message at the time it was logged.
|
||||
//
|
||||
// The formatting is already applied to the message and depends on the config
|
||||
// like with any other receiver.
|
||||
//
|
||||
// If you would like to inform seelog of an error that happened during the handling of
|
||||
// the message, return a non-nil error. This way you'll end up seeing your error like
|
||||
// any other internal seelog error.
|
||||
ReceiveMessage(message string, level LogLevel, context LogContextInterface) error
|
||||
|
||||
// AfterParse is called immediately after your custom receiver is instantiated by
|
||||
// the xml config parser. So, if you need to do any startup logic after config parsing,
|
||||
// like opening file or allocating any resources after the receiver is instantiated, do it here.
|
||||
//
|
||||
// If this func returns a non-nil error, then the loading procedure will fail. E.g.
|
||||
// if you are loading a seelog xml config, the parser would not finish the loading
|
||||
// procedure and inform about an error like with any other config error.
|
||||
//
|
||||
// If your custom logger needs some configuration, you can use custom attributes in
|
||||
// your config. Check CustomReceiverInitArgs.XmlCustomAttrs comments.
|
||||
//
|
||||
// IMPORTANT: This func is NOT called when the LoggerFromCustomReceiver func is used
|
||||
// to create seelog proxy logger using the custom receiver. This func is only called when
|
||||
// receiver is instantiated from a config.
|
||||
AfterParse(initArgs CustomReceiverInitArgs) error
|
||||
|
||||
// Flush is called when the custom receiver gets a 'flush' directive from a
|
||||
// parent receiver. If custom receiver implements some kind of buffering or
|
||||
// queing, then the appropriate reaction on a flush message is synchronous
|
||||
// flushing of all those queues/buffers. If custom receiver doesn't have
|
||||
// such mechanisms, then flush implementation may be left empty.
|
||||
Flush()
|
||||
|
||||
// Close is called when the custom receiver gets a 'close' directive from a
|
||||
// parent receiver. This happens when a top-level seelog dispatcher is sending
|
||||
// 'close' to all child nodes and it means that current seelog logger is being closed.
|
||||
// If you need to do any cleanup after your custom receiver is done, you should do
|
||||
// it here.
|
||||
Close() error
|
||||
}
|
||||
|
||||
type customReceiverDispatcher struct {
|
||||
formatter *formatter
|
||||
innerReceiver CustomReceiver
|
||||
customReceiverName string
|
||||
usedArgs CustomReceiverInitArgs
|
||||
}
|
||||
|
||||
// NewCustomReceiverDispatcher creates a customReceiverDispatcher which dispatches data to a specific receiver created
|
||||
// using a <custom> tag in the config file.
|
||||
func NewCustomReceiverDispatcher(formatter *formatter, customReceiverName string, cArgs CustomReceiverInitArgs) (*customReceiverDispatcher, error) {
|
||||
if formatter == nil {
|
||||
return nil, errors.New("formatter cannot be nil")
|
||||
}
|
||||
if len(customReceiverName) == 0 {
|
||||
return nil, errors.New("custom receiver name cannot be empty")
|
||||
}
|
||||
|
||||
creceiver, err := customReceiverByName(customReceiverName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = creceiver.AfterParse(cArgs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
disp := &customReceiverDispatcher{formatter, creceiver, customReceiverName, cArgs}
|
||||
|
||||
return disp, nil
|
||||
}
|
||||
|
||||
// NewCustomReceiverDispatcherByValue is basically the same as NewCustomReceiverDispatcher, but using
|
||||
// a specific CustomReceiver value instead of instantiating a new one by type.
|
||||
func NewCustomReceiverDispatcherByValue(formatter *formatter, customReceiver CustomReceiver, name string, cArgs CustomReceiverInitArgs) (*customReceiverDispatcher, error) {
|
||||
if formatter == nil {
|
||||
return nil, errors.New("formatter cannot be nil")
|
||||
}
|
||||
if customReceiver == nil {
|
||||
return nil, errors.New("customReceiver cannot be nil")
|
||||
}
|
||||
disp := &customReceiverDispatcher{formatter, customReceiver, name, cArgs}
|
||||
|
||||
return disp, nil
|
||||
}
|
||||
|
||||
// CustomReceiver implementation. Check CustomReceiver comments.
|
||||
func (disp *customReceiverDispatcher) Dispatch(
|
||||
message string,
|
||||
level LogLevel,
|
||||
context LogContextInterface,
|
||||
errorFunc func(err error)) {
|
||||
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
errorFunc(fmt.Errorf("panic in custom receiver '%s'.Dispatch: %s", reflect.TypeOf(disp.innerReceiver), err))
|
||||
}
|
||||
}()
|
||||
|
||||
err := disp.innerReceiver.ReceiveMessage(disp.formatter.Format(message, level, context), level, context)
|
||||
if err != nil {
|
||||
errorFunc(err)
|
||||
}
|
||||
}
|
||||
|
||||
// CustomReceiver implementation. Check CustomReceiver comments.
|
||||
func (disp *customReceiverDispatcher) Flush() {
|
||||
disp.innerReceiver.Flush()
|
||||
}
|
||||
|
||||
// CustomReceiver implementation. Check CustomReceiver comments.
|
||||
func (disp *customReceiverDispatcher) Close() error {
|
||||
disp.innerReceiver.Flush()
|
||||
|
||||
err := disp.innerReceiver.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (disp *customReceiverDispatcher) String() string {
|
||||
datas := ""
|
||||
skeys := make([]string, 0, len(disp.usedArgs.XmlCustomAttrs))
|
||||
for i := range disp.usedArgs.XmlCustomAttrs {
|
||||
skeys = append(skeys, i)
|
||||
}
|
||||
sort.Strings(skeys)
|
||||
for _, key := range skeys {
|
||||
datas += fmt.Sprintf("<%s, %s> ", key, disp.usedArgs.XmlCustomAttrs[key])
|
||||
}
|
||||
|
||||
str := fmt.Sprintf("Custom receiver %s [fmt='%s'],[data='%s'],[inner='%s']\n",
|
||||
disp.customReceiverName, disp.formatter.String(), datas, disp.innerReceiver)
|
||||
|
||||
return str
|
||||
}
|
|
@ -0,0 +1,189 @@
|
|||
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||
// list of conditions and the following disclaimer.
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package seelog
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// A dispatcherInterface is used to dispatch message to all underlying receivers.
|
||||
// Dispatch logic depends on given context and log level. Any errors are reported using errorFunc.
|
||||
// Also, as underlying receivers may have a state, dispatcher has a ShuttingDown method which performs
|
||||
// an immediate cleanup of all data that is stored in the receivers
|
||||
type dispatcherInterface interface {
|
||||
flusherInterface
|
||||
io.Closer
|
||||
Dispatch(message string, level LogLevel, context LogContextInterface, errorFunc func(err error))
|
||||
}
|
||||
|
||||
type dispatcher struct {
|
||||
formatter *formatter
|
||||
writers []*formattedWriter
|
||||
dispatchers []dispatcherInterface
|
||||
}
|
||||
|
||||
// Creates a dispatcher which dispatches data to a list of receivers.
|
||||
// Each receiver should be either a Dispatcher or io.Writer, otherwise an error will be returned
|
||||
func createDispatcher(formatter *formatter, receivers []interface{}) (*dispatcher, error) {
|
||||
if formatter == nil {
|
||||
return nil, errors.New("formatter cannot be nil")
|
||||
}
|
||||
if receivers == nil || len(receivers) == 0 {
|
||||
return nil, errors.New("receivers cannot be nil or empty")
|
||||
}
|
||||
|
||||
disp := &dispatcher{formatter, make([]*formattedWriter, 0), make([]dispatcherInterface, 0)}
|
||||
for _, receiver := range receivers {
|
||||
writer, ok := receiver.(*formattedWriter)
|
||||
if ok {
|
||||
disp.writers = append(disp.writers, writer)
|
||||
continue
|
||||
}
|
||||
|
||||
ioWriter, ok := receiver.(io.Writer)
|
||||
if ok {
|
||||
writer, err := NewFormattedWriter(ioWriter, disp.formatter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
disp.writers = append(disp.writers, writer)
|
||||
continue
|
||||
}
|
||||
|
||||
dispInterface, ok := receiver.(dispatcherInterface)
|
||||
if ok {
|
||||
disp.dispatchers = append(disp.dispatchers, dispInterface)
|
||||
continue
|
||||
}
|
||||
|
||||
return nil, errors.New("method can receive either io.Writer or dispatcherInterface")
|
||||
}
|
||||
|
||||
return disp, nil
|
||||
}
|
||||
|
||||
func (disp *dispatcher) Dispatch(
|
||||
message string,
|
||||
level LogLevel,
|
||||
context LogContextInterface,
|
||||
errorFunc func(err error)) {
|
||||
|
||||
for _, writer := range disp.writers {
|
||||
err := writer.Write(message, level, context)
|
||||
if err != nil {
|
||||
errorFunc(err)
|
||||
}
|
||||
}
|
||||
|
||||
for _, dispInterface := range disp.dispatchers {
|
||||
dispInterface.Dispatch(message, level, context, errorFunc)
|
||||
}
|
||||
}
|
||||
|
||||
// Flush goes through all underlying writers which implement flusherInterface interface
|
||||
// and closes them. Recursively performs the same action for underlying dispatchers
|
||||
func (disp *dispatcher) Flush() {
|
||||
for _, disp := range disp.Dispatchers() {
|
||||
disp.Flush()
|
||||
}
|
||||
|
||||
for _, formatWriter := range disp.Writers() {
|
||||
flusher, ok := formatWriter.Writer().(flusherInterface)
|
||||
if ok {
|
||||
flusher.Flush()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Close goes through all underlying writers which implement io.Closer interface
|
||||
// and closes them. Recursively performs the same action for underlying dispatchers
|
||||
// Before closing, writers are flushed to prevent loss of any buffered data, so
|
||||
// a call to Flush() func before Close() is not necessary
|
||||
func (disp *dispatcher) Close() error {
|
||||
for _, disp := range disp.Dispatchers() {
|
||||
disp.Flush()
|
||||
err := disp.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, formatWriter := range disp.Writers() {
|
||||
flusher, ok := formatWriter.Writer().(flusherInterface)
|
||||
if ok {
|
||||
flusher.Flush()
|
||||
}
|
||||
|
||||
closer, ok := formatWriter.Writer().(io.Closer)
|
||||
if ok {
|
||||
err := closer.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (disp *dispatcher) Writers() []*formattedWriter {
|
||||
return disp.writers
|
||||
}
|
||||
|
||||
func (disp *dispatcher) Dispatchers() []dispatcherInterface {
|
||||
return disp.dispatchers
|
||||
}
|
||||
|
||||
func (disp *dispatcher) String() string {
|
||||
str := "formatter: " + disp.formatter.String() + "\n"
|
||||
|
||||
str += " ->Dispatchers:"
|
||||
|
||||
if len(disp.dispatchers) == 0 {
|
||||
str += "none\n"
|
||||
} else {
|
||||
str += "\n"
|
||||
|
||||
for _, disp := range disp.dispatchers {
|
||||
str += fmt.Sprintf(" ->%s", disp)
|
||||
}
|
||||
}
|
||||
|
||||
str += " ->Writers:"
|
||||
|
||||
if len(disp.writers) == 0 {
|
||||
str += "none\n"
|
||||
} else {
|
||||
str += "\n"
|
||||
|
||||
for _, writer := range disp.writers {
|
||||
str += fmt.Sprintf(" ->%s\n", writer)
|
||||
}
|
||||
}
|
||||
|
||||
return str
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||
// list of conditions and the following disclaimer.
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package seelog
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// A filterDispatcher writes the given message to underlying receivers only if message log level
|
||||
// is in the allowed list.
|
||||
type filterDispatcher struct {
|
||||
*dispatcher
|
||||
allowList map[LogLevel]bool
|
||||
}
|
||||
|
||||
// NewFilterDispatcher creates a new filterDispatcher using a list of allowed levels.
|
||||
func NewFilterDispatcher(formatter *formatter, receivers []interface{}, allowList ...LogLevel) (*filterDispatcher, error) {
|
||||
disp, err := createDispatcher(formatter, receivers)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
allows := make(map[LogLevel]bool)
|
||||
for _, allowLevel := range allowList {
|
||||
allows[allowLevel] = true
|
||||
}
|
||||
|
||||
return &filterDispatcher{disp, allows}, nil
|
||||
}
|
||||
|
||||
func (filter *filterDispatcher) Dispatch(
|
||||
message string,
|
||||
level LogLevel,
|
||||
context LogContextInterface,
|
||||
errorFunc func(err error)) {
|
||||
isAllowed, ok := filter.allowList[level]
|
||||
if ok && isAllowed {
|
||||
filter.dispatcher.Dispatch(message, level, context, errorFunc)
|
||||
}
|
||||
}
|
||||
|
||||
func (filter *filterDispatcher) String() string {
|
||||
return fmt.Sprintf("filterDispatcher ->\n%s", filter.dispatcher)
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||
// list of conditions and the following disclaimer.
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package seelog
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// A splitDispatcher just writes the given message to underlying receivers. (Splits the message stream.)
|
||||
type splitDispatcher struct {
|
||||
*dispatcher
|
||||
}
|
||||
|
||||
func NewSplitDispatcher(formatter *formatter, receivers []interface{}) (*splitDispatcher, error) {
|
||||
disp, err := createDispatcher(formatter, receivers)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &splitDispatcher{disp}, nil
|
||||
}
|
||||
|
||||
func (splitter *splitDispatcher) String() string {
|
||||
return fmt.Sprintf("splitDispatcher ->\n%s", splitter.dispatcher.String())
|
||||
}
|
|
@ -0,0 +1,175 @@
|
|||
// Copyright (c) 2014 - Cloud Instruments Co., Ltd.
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||
// list of conditions and the following disclaimer.
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
/*
|
||||
Package seelog implements logging functionality with flexible dispatching, filtering, and formatting.
|
||||
|
||||
Creation
|
||||
|
||||
To create a logger, use one of the following constructors:
|
||||
func LoggerFromConfigAsBytes
|
||||
func LoggerFromConfigAsFile
|
||||
func LoggerFromConfigAsString
|
||||
func LoggerFromWriterWithMinLevel
|
||||
func LoggerFromWriterWithMinLevelAndFormat
|
||||
func LoggerFromCustomReceiver (check https://github.com/cihub/seelog/wiki/Custom-receivers)
|
||||
Example:
|
||||
import log "github.com/cihub/seelog"
|
||||
|
||||
func main() {
|
||||
logger, err := log.LoggerFromConfigAsFile("seelog.xml")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer logger.Flush()
|
||||
... use logger ...
|
||||
}
|
||||
The "defer" line is important because if you are using asynchronous logger behavior, without this line you may end up losing some
|
||||
messages when you close your application because they are processed in another non-blocking goroutine. To avoid that you
|
||||
explicitly defer flushing all messages before closing.
|
||||
|
||||
Usage
|
||||
|
||||
Logger created using one of the LoggerFrom* funcs can be used directly by calling one of the main log funcs.
|
||||
Example:
|
||||
import log "github.com/cihub/seelog"
|
||||
|
||||
func main() {
|
||||
logger, err := log.LoggerFromConfigAsFile("seelog.xml")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer logger.Flush()
|
||||
logger.Trace("test")
|
||||
logger.Debugf("var = %s", "abc")
|
||||
}
|
||||
|
||||
Having loggers as variables is convenient if you are writing your own package with internal logging or if you have
|
||||
several loggers with different options.
|
||||
But for most standalone apps it is more convenient to use package level funcs and vars. There is a package level
|
||||
var 'Current' made for it. You can replace it with another logger using 'ReplaceLogger' and then use package level funcs:
|
||||
import log "github.com/cihub/seelog"
|
||||
|
||||
func main() {
|
||||
logger, err := log.LoggerFromConfigAsFile("seelog.xml")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
log.ReplaceLogger(logger)
|
||||
defer log.Flush()
|
||||
log.Trace("test")
|
||||
log.Debugf("var = %s", "abc")
|
||||
}
|
||||
Last lines
|
||||
log.Trace("test")
|
||||
log.Debugf("var = %s", "abc")
|
||||
do the same as
|
||||
log.Current.Trace("test")
|
||||
log.Current.Debugf("var = %s", "abc")
|
||||
In this example the 'Current' logger was replaced using a 'ReplaceLogger' call and became equal to 'logger' variable created from config.
|
||||
This way you are able to use package level funcs instead of passing the logger variable.
|
||||
|
||||
Configuration
|
||||
|
||||
Main seelog point is to configure logger via config files and not the code.
|
||||
The configuration is read by LoggerFrom* funcs. These funcs read xml configuration from different sources and try
|
||||
to create a logger using it.
|
||||
|
||||
All the configuration features are covered in detail in the official wiki: https://github.com/cihub/seelog/wiki.
|
||||
There are many sections covering different aspects of seelog, but the most important for understanding configs are:
|
||||
https://github.com/cihub/seelog/wiki/Constraints-and-exceptions
|
||||
https://github.com/cihub/seelog/wiki/Dispatchers-and-receivers
|
||||
https://github.com/cihub/seelog/wiki/Formatting
|
||||
https://github.com/cihub/seelog/wiki/Logger-types
|
||||
After you understand these concepts, check the 'Reference' section on the main wiki page to get the up-to-date
|
||||
list of dispatchers, receivers, formats, and logger types.
|
||||
|
||||
Here is an example config with all these features:
|
||||
<seelog type="adaptive" mininterval="2000000" maxinterval="100000000" critmsgcount="500" minlevel="debug">
|
||||
<exceptions>
|
||||
<exception filepattern="test*" minlevel="error"/>
|
||||
</exceptions>
|
||||
<outputs formatid="all">
|
||||
<file path="all.log"/>
|
||||
<filter levels="info">
|
||||
<console formatid="fmtinfo"/>
|
||||
</filter>
|
||||
<filter levels="error,critical" formatid="fmterror">
|
||||
<console/>
|
||||
<file path="errors.log"/>
|
||||
</filter>
|
||||
</outputs>
|
||||
<formats>
|
||||
<format id="fmtinfo" format="[%Level] [%Time] %Msg%n"/>
|
||||
<format id="fmterror" format="[%LEVEL] [%Time] [%FuncShort @ %File.%Line] %Msg%n"/>
|
||||
<format id="all" format="[%Level] [%Time] [@ %File.%Line] %Msg%n"/>
|
||||
<format id="criticalemail" format="Critical error on our server!\n %Time %Date %RelFile %Func %Msg \nSent by Seelog"/>
|
||||
</formats>
|
||||
</seelog>
|
||||
This config represents a logger with adaptive timeout between log messages (check logger types reference) which
|
||||
logs to console, all.log, and errors.log depending on the log level. Its output formats also depend on log level. This logger will only
|
||||
use log level 'debug' and higher (minlevel is set) for all files with names that don't start with 'test'. For files starting with 'test'
|
||||
this logger prohibits all levels below 'error'.
|
||||
|
||||
Configuration using code
|
||||
|
||||
Although configuration using code is not recommended, it is sometimes needed and it is possible to do with seelog. Basically, what
|
||||
you need to do to get started is to create constraints, exceptions and a dispatcher tree (same as with config). Most of the New*
|
||||
functions in this package are used to provide such capabilities.
|
||||
|
||||
Here is an example of configuration in code, that demonstrates an async loop logger that logs to a simple split dispatcher with
|
||||
a console receiver using a specified format and is filtered using a top-level min-max constraints and one expection for
|
||||
the 'main.go' file. So, this is basically a demonstration of configuration of most of the features:
|
||||
|
||||
package main
|
||||
|
||||
import log "github.com/cihub/seelog"
|
||||
|
||||
func main() {
|
||||
defer log.Flush()
|
||||
log.Info("Hello from Seelog!")
|
||||
|
||||
consoleWriter, _ := log.NewConsoleWriter()
|
||||
formatter, _ := log.NewFormatter("%Level %Msg %File%n")
|
||||
root, _ := log.NewSplitDispatcher(formatter, []interface{}{consoleWriter})
|
||||
constraints, _ := log.NewMinMaxConstraints(log.TraceLvl, log.CriticalLvl)
|
||||
specificConstraints, _ := log.NewListConstraints([]log.LogLevel{log.InfoLvl, log.ErrorLvl})
|
||||
ex, _ := log.NewLogLevelException("*", "*main.go", specificConstraints)
|
||||
exceptions := []*log.LogLevelException{ex}
|
||||
|
||||
logger := log.NewAsyncLoopLogger(log.NewLoggerConfig(constraints, exceptions, root))
|
||||
log.ReplaceLogger(logger)
|
||||
|
||||
log.Trace("This should not be seen")
|
||||
log.Debug("This should not be seen")
|
||||
log.Info("Test")
|
||||
log.Error("Test2")
|
||||
}
|
||||
|
||||
Examples
|
||||
|
||||
To learn seelog features faster you should check the examples package: https://github.com/cihub/seelog-examples
|
||||
It contains many example configs and usecases.
|
||||
*/
|
||||
package seelog
|
|
@ -0,0 +1,461 @@
|
|||
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||
// list of conditions and the following disclaimer.
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package seelog
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// FormatterSymbol is a special symbol used in config files to mark special format aliases.
|
||||
const (
|
||||
FormatterSymbol = '%'
|
||||
)
|
||||
|
||||
const (
|
||||
formatterParameterStart = '('
|
||||
formatterParameterEnd = ')'
|
||||
)
|
||||
|
||||
// Time and date formats used for %Date and %Time aliases.
|
||||
const (
|
||||
DateDefaultFormat = "2006-01-02"
|
||||
TimeFormat = "15:04:05"
|
||||
)
|
||||
|
||||
var DefaultMsgFormat = "%Ns [%Level] %Msg%n"
|
||||
|
||||
var (
|
||||
DefaultFormatter *formatter
|
||||
msgonlyformatter *formatter
|
||||
)
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
if DefaultFormatter, err = NewFormatter(DefaultMsgFormat); err != nil {
|
||||
reportInternalError(fmt.Errorf("error during creating DefaultFormatter: %s", err))
|
||||
}
|
||||
if msgonlyformatter, err = NewFormatter("%Msg"); err != nil {
|
||||
reportInternalError(fmt.Errorf("error during creating msgonlyformatter: %s", err))
|
||||
}
|
||||
}
|
||||
|
||||
// FormatterFunc represents one formatter object that starts with '%' sign in the 'format' attribute
|
||||
// of the 'format' config item. These special symbols are replaced with context values or special
|
||||
// strings when message is written to byte receiver.
|
||||
//
|
||||
// Check https://github.com/cihub/seelog/wiki/Formatting for details.
|
||||
// Full list (with descriptions) of formatters: https://github.com/cihub/seelog/wiki/Format-reference
|
||||
//
|
||||
// FormatterFunc takes raw log message, level, log context and returns a string, number (of any type) or any object
|
||||
// that can be evaluated as string.
|
||||
type FormatterFunc func(message string, level LogLevel, context LogContextInterface) interface{}
|
||||
|
||||
// FormatterFuncCreator is a factory of FormatterFunc objects. It is used to generate parameterized
|
||||
// formatters (such as %Date or %EscM) and custom user formatters.
|
||||
type FormatterFuncCreator func(param string) FormatterFunc
|
||||
|
||||
var formatterFuncs = map[string]FormatterFunc{
|
||||
"Level": formatterLevel,
|
||||
"Lev": formatterLev,
|
||||
"LEVEL": formatterLEVEL,
|
||||
"LEV": formatterLEV,
|
||||
"l": formatterl,
|
||||
"Msg": formatterMsg,
|
||||
"FullPath": formatterFullPath,
|
||||
"File": formatterFile,
|
||||
"RelFile": formatterRelFile,
|
||||
"Func": FormatterFunction,
|
||||
"FuncShort": FormatterFunctionShort,
|
||||
"Line": formatterLine,
|
||||
"Time": formatterTime,
|
||||
"UTCTime": formatterUTCTime,
|
||||
"Ns": formatterNs,
|
||||
"UTCNs": formatterUTCNs,
|
||||
"n": formattern,
|
||||
"t": formattert,
|
||||
}
|
||||
|
||||
var formatterFuncsParameterized = map[string]FormatterFuncCreator{
|
||||
"Date": createDateTimeFormatterFunc,
|
||||
"UTCDate": createUTCDateTimeFormatterFunc,
|
||||
"EscM": createANSIEscapeFunc,
|
||||
}
|
||||
|
||||
func errorAliasReserved(name string) error {
|
||||
return fmt.Errorf("cannot use '%s' as custom formatter name. Name is reserved", name)
|
||||
}
|
||||
|
||||
// RegisterCustomFormatter registers a new custom formatter factory with a given name. If returned error is nil,
|
||||
// then this name (prepended by '%' symbol) can be used in 'format' attributes in configuration and
|
||||
// it will be treated like the standard parameterized formatter identifiers.
|
||||
//
|
||||
// RegisterCustomFormatter needs to be called before creating a logger for it to take effect. The general recommendation
|
||||
// is to call it once in 'init' func of your application or any initializer func.
|
||||
//
|
||||
// For usage examples, check https://github.com/cihub/seelog/wiki/Custom-formatters.
|
||||
//
|
||||
// Name must only consist of letters (unicode.IsLetter).
|
||||
//
|
||||
// Name must not be one of the already registered standard formatter names
|
||||
// (https://github.com/cihub/seelog/wiki/Format-reference) and previously registered
|
||||
// custom format names. To avoid any potential name conflicts (in future releases), it is recommended
|
||||
// to start your custom formatter name with a namespace (e.g. 'MyCompanySomething') or a 'Custom' keyword.
|
||||
func RegisterCustomFormatter(name string, creator FormatterFuncCreator) error {
|
||||
if _, ok := formatterFuncs[name]; ok {
|
||||
return errorAliasReserved(name)
|
||||
}
|
||||
if _, ok := formatterFuncsParameterized[name]; ok {
|
||||
return errorAliasReserved(name)
|
||||
}
|
||||
formatterFuncsParameterized[name] = creator
|
||||
return nil
|
||||
}
|
||||
|
||||
// formatter is used to write messages in a specific format, inserting such additional data
|
||||
// as log level, date/time, etc.
|
||||
type formatter struct {
|
||||
fmtStringOriginal string
|
||||
fmtString string
|
||||
formatterFuncs []FormatterFunc
|
||||
}
|
||||
|
||||
// NewFormatter creates a new formatter using a format string
|
||||
func NewFormatter(formatString string) (*formatter, error) {
|
||||
fmtr := new(formatter)
|
||||
fmtr.fmtStringOriginal = formatString
|
||||
if err := buildFormatterFuncs(fmtr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return fmtr, nil
|
||||
}
|
||||
|
||||
func buildFormatterFuncs(formatter *formatter) error {
|
||||
var (
|
||||
fsbuf = new(bytes.Buffer)
|
||||
fsolm1 = len(formatter.fmtStringOriginal) - 1
|
||||
)
|
||||
for i := 0; i <= fsolm1; i++ {
|
||||
if char := formatter.fmtStringOriginal[i]; char != FormatterSymbol {
|
||||
fsbuf.WriteByte(char)
|
||||
continue
|
||||
}
|
||||
// Check if the index is at the end of the string.
|
||||
if i == fsolm1 {
|
||||
return fmt.Errorf("format error: %c cannot be last symbol", FormatterSymbol)
|
||||
}
|
||||
// Check if the formatter symbol is doubled and skip it as nonmatching.
|
||||
if formatter.fmtStringOriginal[i+1] == FormatterSymbol {
|
||||
fsbuf.WriteRune(FormatterSymbol)
|
||||
i++
|
||||
continue
|
||||
}
|
||||
function, ni, err := formatter.extractFormatterFunc(i + 1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Append formatting string "%v".
|
||||
fsbuf.Write([]byte{37, 118})
|
||||
i = ni
|
||||
formatter.formatterFuncs = append(formatter.formatterFuncs, function)
|
||||
}
|
||||
formatter.fmtString = fsbuf.String()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (formatter *formatter) extractFormatterFunc(index int) (FormatterFunc, int, error) {
|
||||
letterSequence := formatter.extractLetterSequence(index)
|
||||
if len(letterSequence) == 0 {
|
||||
return nil, 0, fmt.Errorf("format error: lack of formatter after %c at %d", FormatterSymbol, index)
|
||||
}
|
||||
|
||||
function, formatterLength, ok := formatter.findFormatterFunc(letterSequence)
|
||||
if ok {
|
||||
return function, index + formatterLength - 1, nil
|
||||
}
|
||||
|
||||
function, formatterLength, ok, err := formatter.findFormatterFuncParametrized(letterSequence, index)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
if ok {
|
||||
return function, index + formatterLength - 1, nil
|
||||
}
|
||||
|
||||
return nil, 0, errors.New("format error: unrecognized formatter at " + strconv.Itoa(index) + ": " + letterSequence)
|
||||
}
|
||||
|
||||
func (formatter *formatter) extractLetterSequence(index int) string {
|
||||
letters := ""
|
||||
|
||||
bytesToParse := []byte(formatter.fmtStringOriginal[index:])
|
||||
runeCount := utf8.RuneCount(bytesToParse)
|
||||
for i := 0; i < runeCount; i++ {
|
||||
rune, runeSize := utf8.DecodeRune(bytesToParse)
|
||||
bytesToParse = bytesToParse[runeSize:]
|
||||
|
||||
if unicode.IsLetter(rune) {
|
||||
letters += string(rune)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return letters
|
||||
}
|
||||
|
||||
func (formatter *formatter) findFormatterFunc(letters string) (FormatterFunc, int, bool) {
|
||||
currentVerb := letters
|
||||
for i := 0; i < len(letters); i++ {
|
||||
function, ok := formatterFuncs[currentVerb]
|
||||
if ok {
|
||||
return function, len(currentVerb), ok
|
||||
}
|
||||
currentVerb = currentVerb[:len(currentVerb)-1]
|
||||
}
|
||||
|
||||
return nil, 0, false
|
||||
}
|
||||
|
||||
func (formatter *formatter) findFormatterFuncParametrized(letters string, lettersStartIndex int) (FormatterFunc, int, bool, error) {
|
||||
currentVerb := letters
|
||||
for i := 0; i < len(letters); i++ {
|
||||
functionCreator, ok := formatterFuncsParameterized[currentVerb]
|
||||
if ok {
|
||||
parameter := ""
|
||||
parameterLen := 0
|
||||
isVerbEqualsLetters := i == 0 // if not, then letter goes after formatter, and formatter is parameterless
|
||||
if isVerbEqualsLetters {
|
||||
userParameter := ""
|
||||
var err error
|
||||
userParameter, parameterLen, ok, err = formatter.findparameter(lettersStartIndex + len(currentVerb))
|
||||
if ok {
|
||||
parameter = userParameter
|
||||
} else if err != nil {
|
||||
return nil, 0, false, err
|
||||
}
|
||||
}
|
||||
|
||||
return functionCreator(parameter), len(currentVerb) + parameterLen, true, nil
|
||||
}
|
||||
|
||||
currentVerb = currentVerb[:len(currentVerb)-1]
|
||||
}
|
||||
|
||||
return nil, 0, false, nil
|
||||
}
|
||||
|
||||
func (formatter *formatter) findparameter(startIndex int) (string, int, bool, error) {
|
||||
if len(formatter.fmtStringOriginal) == startIndex || formatter.fmtStringOriginal[startIndex] != formatterParameterStart {
|
||||
return "", 0, false, nil
|
||||
}
|
||||
|
||||
endIndex := strings.Index(formatter.fmtStringOriginal[startIndex:], string(formatterParameterEnd))
|
||||
if endIndex == -1 {
|
||||
return "", 0, false, fmt.Errorf("Unmatched parenthesis or invalid parameter at %d: %s",
|
||||
startIndex, formatter.fmtStringOriginal[startIndex:])
|
||||
}
|
||||
endIndex += startIndex
|
||||
|
||||
length := endIndex - startIndex + 1
|
||||
|
||||
return formatter.fmtStringOriginal[startIndex+1 : endIndex], length, true, nil
|
||||
}
|
||||
|
||||
// Format processes a message with special formatters, log level, and context. Returns formatted string
|
||||
// with all formatter identifiers changed to appropriate values.
|
||||
func (formatter *formatter) Format(message string, level LogLevel, context LogContextInterface) string {
|
||||
if len(formatter.formatterFuncs) == 0 {
|
||||
return formatter.fmtString
|
||||
}
|
||||
|
||||
params := make([]interface{}, len(formatter.formatterFuncs))
|
||||
for i, function := range formatter.formatterFuncs {
|
||||
params[i] = function(message, level, context)
|
||||
}
|
||||
|
||||
return fmt.Sprintf(formatter.fmtString, params...)
|
||||
}
|
||||
|
||||
func (formatter *formatter) String() string {
|
||||
return formatter.fmtStringOriginal
|
||||
}
|
||||
|
||||
//=====================================================
|
||||
|
||||
const (
|
||||
wrongLogLevel = "WRONG_LOGLEVEL"
|
||||
wrongEscapeCode = "WRONG_ESCAPE"
|
||||
)
|
||||
|
||||
var levelToString = map[LogLevel]string{
|
||||
TraceLvl: "Trace",
|
||||
DebugLvl: "Debug",
|
||||
InfoLvl: "Info",
|
||||
WarnLvl: "Warn",
|
||||
ErrorLvl: "Error",
|
||||
CriticalLvl: "Critical",
|
||||
Off: "Off",
|
||||
}
|
||||
|
||||
var levelToShortString = map[LogLevel]string{
|
||||
TraceLvl: "Trc",
|
||||
DebugLvl: "Dbg",
|
||||
InfoLvl: "Inf",
|
||||
WarnLvl: "Wrn",
|
||||
ErrorLvl: "Err",
|
||||
CriticalLvl: "Crt",
|
||||
Off: "Off",
|
||||
}
|
||||
|
||||
var levelToShortestString = map[LogLevel]string{
|
||||
TraceLvl: "t",
|
||||
DebugLvl: "d",
|
||||
InfoLvl: "i",
|
||||
WarnLvl: "w",
|
||||
ErrorLvl: "e",
|
||||
CriticalLvl: "c",
|
||||
Off: "o",
|
||||
}
|
||||
|
||||
func formatterLevel(message string, level LogLevel, context LogContextInterface) interface{} {
|
||||
levelStr, ok := levelToString[level]
|
||||
if !ok {
|
||||
return wrongLogLevel
|
||||
}
|
||||
return levelStr
|
||||
}
|
||||
|
||||
func formatterLev(message string, level LogLevel, context LogContextInterface) interface{} {
|
||||
levelStr, ok := levelToShortString[level]
|
||||
if !ok {
|
||||
return wrongLogLevel
|
||||
}
|
||||
return levelStr
|
||||
}
|
||||
|
||||
func formatterLEVEL(message string, level LogLevel, context LogContextInterface) interface{} {
|
||||
return strings.ToTitle(formatterLevel(message, level, context).(string))
|
||||
}
|
||||
|
||||
func formatterLEV(message string, level LogLevel, context LogContextInterface) interface{} {
|
||||
return strings.ToTitle(formatterLev(message, level, context).(string))
|
||||
}
|
||||
|
||||
func formatterl(message string, level LogLevel, context LogContextInterface) interface{} {
|
||||
levelStr, ok := levelToShortestString[level]
|
||||
if !ok {
|
||||
return wrongLogLevel
|
||||
}
|
||||
return levelStr
|
||||
}
|
||||
|
||||
func formatterMsg(message string, level LogLevel, context LogContextInterface) interface{} {
|
||||
return message
|
||||
}
|
||||
|
||||
func formatterFullPath(message string, level LogLevel, context LogContextInterface) interface{} {
|
||||
return context.FullPath()
|
||||
}
|
||||
|
||||
func formatterFile(message string, level LogLevel, context LogContextInterface) interface{} {
|
||||
return context.FileName()
|
||||
}
|
||||
|
||||
func formatterRelFile(message string, level LogLevel, context LogContextInterface) interface{} {
|
||||
return context.ShortPath()
|
||||
}
|
||||
|
||||
func FormatterFunction(message string, level LogLevel, context LogContextInterface) interface{} {
|
||||
return context.Func()
|
||||
}
|
||||
|
||||
func FormatterFunctionShort(message string, level LogLevel, context LogContextInterface) interface{} {
|
||||
f := context.Func()
|
||||
spl := strings.Split(f, ".")
|
||||
return spl[len(spl)-1]
|
||||
}
|
||||
|
||||
func formatterLine(message string, level LogLevel, context LogContextInterface) interface{} {
|
||||
return context.Line()
|
||||
}
|
||||
|
||||
func formatterTime(message string, level LogLevel, context LogContextInterface) interface{} {
|
||||
return context.CallTime().Format(TimeFormat)
|
||||
}
|
||||
|
||||
func formatterUTCTime(message string, level LogLevel, context LogContextInterface) interface{} {
|
||||
return context.CallTime().UTC().Format(TimeFormat)
|
||||
}
|
||||
|
||||
func formatterNs(message string, level LogLevel, context LogContextInterface) interface{} {
|
||||
return context.CallTime().UnixNano()
|
||||
}
|
||||
|
||||
func formatterUTCNs(message string, level LogLevel, context LogContextInterface) interface{} {
|
||||
return context.CallTime().UTC().UnixNano()
|
||||
}
|
||||
|
||||
func formattern(message string, level LogLevel, context LogContextInterface) interface{} {
|
||||
return "\n"
|
||||
}
|
||||
|
||||
func formattert(message string, level LogLevel, context LogContextInterface) interface{} {
|
||||
return "\t"
|
||||
}
|
||||
|
||||
func createDateTimeFormatterFunc(dateTimeFormat string) FormatterFunc {
|
||||
format := dateTimeFormat
|
||||
if format == "" {
|
||||
format = DateDefaultFormat
|
||||
}
|
||||
return func(message string, level LogLevel, context LogContextInterface) interface{} {
|
||||
return context.CallTime().Format(format)
|
||||
}
|
||||
}
|
||||
|
||||
func createUTCDateTimeFormatterFunc(dateTimeFormat string) FormatterFunc {
|
||||
format := dateTimeFormat
|
||||
if format == "" {
|
||||
format = DateDefaultFormat
|
||||
}
|
||||
return func(message string, level LogLevel, context LogContextInterface) interface{} {
|
||||
return context.CallTime().UTC().Format(format)
|
||||
}
|
||||
}
|
||||
|
||||
func createANSIEscapeFunc(escapeCodeString string) FormatterFunc {
|
||||
return func(message string, level LogLevel, context LogContextInterface) interface{} {
|
||||
if len(escapeCodeString) == 0 {
|
||||
return wrongEscapeCode
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%c[%sm", 0x1B, escapeCodeString)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package seelog
|
||||
|
||||
// Base struct for custom errors.
|
||||
type baseError struct {
|
||||
message string
|
||||
}
|
||||
|
||||
func (be baseError) Error() string {
|
||||
return be.message
|
||||
}
|
|
@ -0,0 +1,403 @@
|
|||
package seelog
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// File and directory permitions.
|
||||
const (
|
||||
defaultFilePermissions = 0666
|
||||
defaultDirectoryPermissions = 0767
|
||||
)
|
||||
|
||||
const (
|
||||
// Max number of directories can be read asynchronously.
|
||||
maxDirNumberReadAsync = 1000
|
||||
)
|
||||
|
||||
type cannotOpenFileError struct {
|
||||
baseError
|
||||
}
|
||||
|
||||
func newCannotOpenFileError(fname string) *cannotOpenFileError {
|
||||
return &cannotOpenFileError{baseError{message: "Cannot open file: " + fname}}
|
||||
}
|
||||
|
||||
type notDirectoryError struct {
|
||||
baseError
|
||||
}
|
||||
|
||||
func newNotDirectoryError(dname string) *notDirectoryError {
|
||||
return ¬DirectoryError{baseError{message: dname + " is not directory"}}
|
||||
}
|
||||
|
||||
// fileFilter is a filtering criteria function for '*os.File'.
|
||||
// Must return 'false' to set aside the given file.
|
||||
type fileFilter func(os.FileInfo, *os.File) bool
|
||||
|
||||
// filePathFilter is a filtering creteria function for file path.
|
||||
// Must return 'false' to set aside the given file.
|
||||
type filePathFilter func(filePath string) bool
|
||||
|
||||
// GetSubdirNames returns a list of directories found in
|
||||
// the given one with dirPath.
|
||||
func getSubdirNames(dirPath string) ([]string, error) {
|
||||
fi, err := os.Stat(dirPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !fi.IsDir() {
|
||||
return nil, newNotDirectoryError(dirPath)
|
||||
}
|
||||
dd, err := os.Open(dirPath)
|
||||
// Cannot open file.
|
||||
if err != nil {
|
||||
if dd != nil {
|
||||
dd.Close()
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
defer dd.Close()
|
||||
// TODO: Improve performance by buffering reading.
|
||||
allEntities, err := dd.Readdir(-1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
subDirs := []string{}
|
||||
for _, entity := range allEntities {
|
||||
if entity.IsDir() {
|
||||
subDirs = append(subDirs, entity.Name())
|
||||
}
|
||||
}
|
||||
return subDirs, nil
|
||||
}
|
||||
|
||||
// getSubdirAbsPaths recursively visit all the subdirectories
|
||||
// starting from the given directory and returns absolute paths for them.
|
||||
func getAllSubdirAbsPaths(dirPath string) (res []string, err error) {
|
||||
dps, err := getSubdirAbsPaths(dirPath)
|
||||
if err != nil {
|
||||
res = []string{}
|
||||
return
|
||||
}
|
||||
res = append(res, dps...)
|
||||
for _, dp := range dps {
|
||||
sdps, err := getAllSubdirAbsPaths(dp)
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
res = append(res, sdps...)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// getSubdirAbsPaths supplies absolute paths for all subdirectiries in a given directory.
|
||||
// Input: (I1) dirPath - absolute path of a directory in question.
|
||||
// Out: (O1) - slice of subdir asbolute paths; (O2) - error of the operation.
|
||||
// Remark: If error (O2) is non-nil then (O1) is nil and vice versa.
|
||||
func getSubdirAbsPaths(dirPath string) ([]string, error) {
|
||||
sdns, err := getSubdirNames(dirPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rsdns := []string{}
|
||||
for _, sdn := range sdns {
|
||||
rsdns = append(rsdns, filepath.Join(dirPath, sdn))
|
||||
}
|
||||
return rsdns, nil
|
||||
}
|
||||
|
||||
// getOpenFilesInDir supplies a slice of os.File pointers to files located in the directory.
|
||||
// Remark: Ignores files for which fileFilter returns false
|
||||
func getOpenFilesInDir(dirPath string, fFilter fileFilter) ([]*os.File, error) {
|
||||
dfi, err := os.Open(dirPath)
|
||||
if err != nil {
|
||||
return nil, newCannotOpenFileError("Cannot open directory " + dirPath)
|
||||
}
|
||||
defer dfi.Close()
|
||||
// Size of read buffer (i.e. chunk of items read at a time).
|
||||
rbs := 64
|
||||
resFiles := []*os.File{}
|
||||
L:
|
||||
for {
|
||||
// Read directory entities by reasonable chuncks
|
||||
// to prevent overflows on big number of files.
|
||||
fis, e := dfi.Readdir(rbs)
|
||||
switch e {
|
||||
// It's OK.
|
||||
case nil:
|
||||
// Do nothing, just continue cycle.
|
||||
case io.EOF:
|
||||
break L
|
||||
// Something went wrong.
|
||||
default:
|
||||
return nil, e
|
||||
}
|
||||
// THINK: Maybe, use async running.
|
||||
for _, fi := range fis {
|
||||
// NB: On Linux this could be a problem as
|
||||
// there are lots of file types available.
|
||||
if !fi.IsDir() {
|
||||
f, e := os.Open(filepath.Join(dirPath, fi.Name()))
|
||||
if e != nil {
|
||||
if f != nil {
|
||||
f.Close()
|
||||
}
|
||||
// THINK: Add nil as indicator that a problem occurred.
|
||||
resFiles = append(resFiles, nil)
|
||||
continue
|
||||
}
|
||||
// Check filter condition.
|
||||
if fFilter != nil && !fFilter(fi, f) {
|
||||
continue
|
||||
}
|
||||
resFiles = append(resFiles, f)
|
||||
}
|
||||
}
|
||||
}
|
||||
return resFiles, nil
|
||||
}
|
||||
|
||||
func isRegular(m os.FileMode) bool {
|
||||
return m&os.ModeType == 0
|
||||
}
|
||||
|
||||
// getDirFilePaths return full paths of the files located in the directory.
|
||||
// Remark: Ignores files for which fileFilter returns false.
|
||||
func getDirFilePaths(dirPath string, fpFilter filePathFilter, pathIsName bool) ([]string, error) {
|
||||
dfi, err := os.Open(dirPath)
|
||||
if err != nil {
|
||||
return nil, newCannotOpenFileError("Cannot open directory " + dirPath)
|
||||
}
|
||||
defer dfi.Close()
|
||||
|
||||
var absDirPath string
|
||||
if !filepath.IsAbs(dirPath) {
|
||||
absDirPath, err = filepath.Abs(dirPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot get absolute path of directory: %s", err.Error())
|
||||
}
|
||||
} else {
|
||||
absDirPath = dirPath
|
||||
}
|
||||
|
||||
// TODO: check if dirPath is really directory.
|
||||
// Size of read buffer (i.e. chunk of items read at a time).
|
||||
rbs := 2 << 5
|
||||
filePaths := []string{}
|
||||
|
||||
var fp string
|
||||
L:
|
||||
for {
|
||||
// Read directory entities by reasonable chuncks
|
||||
// to prevent overflows on big number of files.
|
||||
fis, e := dfi.Readdir(rbs)
|
||||
switch e {
|
||||
// It's OK.
|
||||
case nil:
|
||||
// Do nothing, just continue cycle.
|
||||
case io.EOF:
|
||||
break L
|
||||
// Indicate that something went wrong.
|
||||
default:
|
||||
return nil, e
|
||||
}
|
||||
// THINK: Maybe, use async running.
|
||||
for _, fi := range fis {
|
||||
// NB: Should work on every Windows and non-Windows OS.
|
||||
if isRegular(fi.Mode()) {
|
||||
if pathIsName {
|
||||
fp = fi.Name()
|
||||
} else {
|
||||
// Build full path of a file.
|
||||
fp = filepath.Join(absDirPath, fi.Name())
|
||||
}
|
||||
// Check filter condition.
|
||||
if fpFilter != nil && !fpFilter(fp) {
|
||||
continue
|
||||
}
|
||||
filePaths = append(filePaths, fp)
|
||||
}
|
||||
}
|
||||
}
|
||||
return filePaths, nil
|
||||
}
|
||||
|
||||
// getOpenFilesByDirectoryAsync runs async reading directories 'dirPaths' and inserts pairs
|
||||
// in map 'filesInDirMap': Key - directory name, value - *os.File slice.
|
||||
func getOpenFilesByDirectoryAsync(
|
||||
dirPaths []string,
|
||||
fFilter fileFilter,
|
||||
filesInDirMap map[string][]*os.File,
|
||||
) error {
|
||||
n := len(dirPaths)
|
||||
if n > maxDirNumberReadAsync {
|
||||
return fmt.Errorf("number of input directories to be read exceeded max value %d", maxDirNumberReadAsync)
|
||||
}
|
||||
type filesInDirResult struct {
|
||||
DirName string
|
||||
Files []*os.File
|
||||
Error error
|
||||
}
|
||||
dirFilesChan := make(chan *filesInDirResult, n)
|
||||
var wg sync.WaitGroup
|
||||
// Register n goroutines which are going to do work.
|
||||
wg.Add(n)
|
||||
for i := 0; i < n; i++ {
|
||||
// Launch asynchronously the piece of work.
|
||||
go func(dirPath string) {
|
||||
fs, e := getOpenFilesInDir(dirPath, fFilter)
|
||||
dirFilesChan <- &filesInDirResult{filepath.Base(dirPath), fs, e}
|
||||
// Mark the current goroutine as finished (work is done).
|
||||
wg.Done()
|
||||
}(dirPaths[i])
|
||||
}
|
||||
// Wait for all goroutines to finish their work.
|
||||
wg.Wait()
|
||||
// Close the error channel to let for-range clause
|
||||
// get all the buffered values without blocking and quit in the end.
|
||||
close(dirFilesChan)
|
||||
for fidr := range dirFilesChan {
|
||||
if fidr.Error == nil {
|
||||
// THINK: What will happen if the key is already present?
|
||||
filesInDirMap[fidr.DirName] = fidr.Files
|
||||
} else {
|
||||
return fidr.Error
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func copyFile(sf *os.File, dst string) (int64, error) {
|
||||
df, err := os.Create(dst)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer df.Close()
|
||||
return io.Copy(df, sf)
|
||||
}
|
||||
|
||||
// fileExists return flag whether a given file exists
|
||||
// and operation error if an unclassified failure occurs.
|
||||
func fileExists(path string) (bool, error) {
|
||||
_, err := os.Stat(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// createDirectory makes directory with a given name
|
||||
// making all parent directories if necessary.
|
||||
func createDirectory(dirPath string) error {
|
||||
var dPath string
|
||||
var err error
|
||||
if !filepath.IsAbs(dirPath) {
|
||||
dPath, err = filepath.Abs(dirPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
dPath = dirPath
|
||||
}
|
||||
exists, err := fileExists(dPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if exists {
|
||||
return nil
|
||||
}
|
||||
return os.MkdirAll(dPath, os.ModeDir)
|
||||
}
|
||||
|
||||
// tryRemoveFile gives a try removing the file
|
||||
// only ignoring an error when the file does not exist.
|
||||
func tryRemoveFile(filePath string) (err error) {
|
||||
err = os.Remove(filePath)
|
||||
if os.IsNotExist(err) {
|
||||
err = nil
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Unzips a specified zip file. Returns filename->filebytes map.
|
||||
func unzip(archiveName string) (map[string][]byte, error) {
|
||||
// Open a zip archive for reading.
|
||||
r, err := zip.OpenReader(archiveName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
// Files to be added to archive
|
||||
// map file name to contents
|
||||
files := make(map[string][]byte)
|
||||
|
||||
// Iterate through the files in the archive,
|
||||
// printing some of their contents.
|
||||
for _, f := range r.File {
|
||||
rc, err := f.Open()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bts, err := ioutil.ReadAll(rc)
|
||||
rcErr := rc.Close()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if rcErr != nil {
|
||||
return nil, rcErr
|
||||
}
|
||||
|
||||
files[f.Name] = bts
|
||||
}
|
||||
|
||||
return files, nil
|
||||
}
|
||||
|
||||
// Creates a zip file with the specified file names and byte contents.
|
||||
func createZip(archiveName string, files map[string][]byte) error {
|
||||
// Create a buffer to write our archive to.
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
// Create a new zip archive.
|
||||
w := zip.NewWriter(buf)
|
||||
|
||||
// Write files
|
||||
for fpath, fcont := range files {
|
||||
f, err := w.Create(fpath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = f.Write([]byte(fcont))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure to check the error on Close.
|
||||
err := w.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(archiveName, buf.Bytes(), defaultFilePermissions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,175 @@
|
|||
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||
// list of conditions and the following disclaimer.
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package seelog
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type xmlNode struct {
|
||||
name string
|
||||
attributes map[string]string
|
||||
children []*xmlNode
|
||||
value string
|
||||
}
|
||||
|
||||
func newNode() *xmlNode {
|
||||
node := new(xmlNode)
|
||||
node.children = make([]*xmlNode, 0)
|
||||
node.attributes = make(map[string]string)
|
||||
return node
|
||||
}
|
||||
|
||||
func (node *xmlNode) String() string {
|
||||
str := fmt.Sprintf("<%s", node.name)
|
||||
|
||||
for attrName, attrVal := range node.attributes {
|
||||
str += fmt.Sprintf(" %s=\"%s\"", attrName, attrVal)
|
||||
}
|
||||
|
||||
str += ">"
|
||||
str += node.value
|
||||
|
||||
if len(node.children) != 0 {
|
||||
for _, child := range node.children {
|
||||
str += fmt.Sprintf("%s", child)
|
||||
}
|
||||
}
|
||||
|
||||
str += fmt.Sprintf("</%s>", node.name)
|
||||
|
||||
return str
|
||||
}
|
||||
|
||||
func (node *xmlNode) unmarshal(startEl xml.StartElement) error {
|
||||
node.name = startEl.Name.Local
|
||||
|
||||
for _, v := range startEl.Attr {
|
||||
_, alreadyExists := node.attributes[v.Name.Local]
|
||||
if alreadyExists {
|
||||
return errors.New("tag '" + node.name + "' has duplicated attribute: '" + v.Name.Local + "'")
|
||||
}
|
||||
node.attributes[v.Name.Local] = v.Value
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (node *xmlNode) add(child *xmlNode) {
|
||||
if node.children == nil {
|
||||
node.children = make([]*xmlNode, 0)
|
||||
}
|
||||
|
||||
node.children = append(node.children, child)
|
||||
}
|
||||
|
||||
func (node *xmlNode) hasChildren() bool {
|
||||
return node.children != nil && len(node.children) > 0
|
||||
}
|
||||
|
||||
//=============================================
|
||||
|
||||
func unmarshalConfig(reader io.Reader) (*xmlNode, error) {
|
||||
xmlParser := xml.NewDecoder(reader)
|
||||
|
||||
config, err := unmarshalNode(xmlParser, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if config == nil {
|
||||
return nil, errors.New("xml has no content")
|
||||
}
|
||||
|
||||
nextConfigEntry, err := unmarshalNode(xmlParser, nil)
|
||||
if nextConfigEntry != nil {
|
||||
return nil, errors.New("xml contains more than one root element")
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func unmarshalNode(xmlParser *xml.Decoder, curToken xml.Token) (node *xmlNode, err error) {
|
||||
firstLoop := true
|
||||
for {
|
||||
var tok xml.Token
|
||||
if firstLoop && curToken != nil {
|
||||
tok = curToken
|
||||
firstLoop = false
|
||||
} else {
|
||||
tok, err = getNextToken(xmlParser)
|
||||
if err != nil || tok == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
switch tt := tok.(type) {
|
||||
case xml.SyntaxError:
|
||||
err = errors.New(tt.Error())
|
||||
return
|
||||
case xml.CharData:
|
||||
value := strings.TrimSpace(string([]byte(tt)))
|
||||
if node != nil {
|
||||
node.value += value
|
||||
}
|
||||
case xml.StartElement:
|
||||
if node == nil {
|
||||
node = newNode()
|
||||
err := node.unmarshal(tt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
childNode, childErr := unmarshalNode(xmlParser, tok)
|
||||
if childErr != nil {
|
||||
return nil, childErr
|
||||
}
|
||||
|
||||
if childNode != nil {
|
||||
node.add(childNode)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
case xml.EndElement:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getNextToken(xmlParser *xml.Decoder) (tok xml.Token, err error) {
|
||||
if tok, err = xmlParser.Token(); err != nil {
|
||||
if err == io.EOF {
|
||||
err = nil
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
|
@ -0,0 +1,307 @@
|
|||
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||
// list of conditions and the following disclaimer.
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package seelog
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
staticFuncCallDepth = 3 // See 'commonLogger.log' method comments
|
||||
loggerFuncCallDepth = 3
|
||||
)
|
||||
|
||||
// Current is the logger used in all package level convenience funcs like 'Trace', 'Debug', 'Flush', etc.
|
||||
var Current LoggerInterface
|
||||
|
||||
// Default logger that is created from an empty config: "<seelog/>". It is not closed by a ReplaceLogger call.
|
||||
var Default LoggerInterface
|
||||
|
||||
// Disabled logger that doesn't produce any output in any circumstances. It is neither closed nor flushed by a ReplaceLogger call.
|
||||
var Disabled LoggerInterface
|
||||
|
||||
var pkgOperationsMutex *sync.Mutex
|
||||
|
||||
func init() {
|
||||
pkgOperationsMutex = new(sync.Mutex)
|
||||
var err error
|
||||
|
||||
if Default == nil {
|
||||
Default, err = LoggerFromConfigAsBytes([]byte("<seelog />"))
|
||||
}
|
||||
|
||||
if Disabled == nil {
|
||||
Disabled, err = LoggerFromConfigAsBytes([]byte("<seelog levels=\"off\"/>"))
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Seelog couldn't start. Error: %s", err.Error()))
|
||||
}
|
||||
|
||||
Current = Default
|
||||
}
|
||||
|
||||
func createLoggerFromFullConfig(config *configForParsing) (LoggerInterface, error) {
|
||||
if config.LogType == syncloggerTypeFromString {
|
||||
return NewSyncLogger(&config.logConfig), nil
|
||||
} else if config.LogType == asyncLooploggerTypeFromString {
|
||||
return NewAsyncLoopLogger(&config.logConfig), nil
|
||||
} else if config.LogType == asyncTimerloggerTypeFromString {
|
||||
logData := config.LoggerData
|
||||
if logData == nil {
|
||||
return nil, errors.New("async timer data not set")
|
||||
}
|
||||
|
||||
asyncInt, ok := logData.(asyncTimerLoggerData)
|
||||
if !ok {
|
||||
return nil, errors.New("invalid async timer data")
|
||||
}
|
||||
|
||||
logger, err := NewAsyncTimerLogger(&config.logConfig, time.Duration(asyncInt.AsyncInterval))
|
||||
if !ok {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return logger, nil
|
||||
} else if config.LogType == adaptiveLoggerTypeFromString {
|
||||
logData := config.LoggerData
|
||||
if logData == nil {
|
||||
return nil, errors.New("adaptive logger parameters not set")
|
||||
}
|
||||
|
||||
adaptData, ok := logData.(adaptiveLoggerData)
|
||||
if !ok {
|
||||
return nil, errors.New("invalid adaptive logger parameters")
|
||||
}
|
||||
|
||||
logger, err := NewAsyncAdaptiveLogger(
|
||||
&config.logConfig,
|
||||
time.Duration(adaptData.MinInterval),
|
||||
time.Duration(adaptData.MaxInterval),
|
||||
adaptData.CriticalMsgCount,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return logger, nil
|
||||
}
|
||||
return nil, errors.New("invalid config log type/data")
|
||||
}
|
||||
|
||||
// UseLogger sets the 'Current' package level logger variable to the specified value.
|
||||
// This variable is used in all Trace/Debug/... package level convenience funcs.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// after calling
|
||||
// seelog.UseLogger(somelogger)
|
||||
// the following:
|
||||
// seelog.Debug("abc")
|
||||
// will be equal to
|
||||
// somelogger.Debug("abc")
|
||||
//
|
||||
// IMPORTANT: UseLogger do NOT close the previous logger (only flushes it). So if
|
||||
// you constantly use it to replace loggers and don't close them in other code, you'll
|
||||
// end up having memory leaks.
|
||||
//
|
||||
// To safely replace loggers, use ReplaceLogger.
|
||||
func UseLogger(logger LoggerInterface) error {
|
||||
if logger == nil {
|
||||
return errors.New("logger can not be nil")
|
||||
}
|
||||
|
||||
pkgOperationsMutex.Lock()
|
||||
defer pkgOperationsMutex.Unlock()
|
||||
|
||||
oldLogger := Current
|
||||
Current = logger
|
||||
|
||||
if oldLogger != nil {
|
||||
oldLogger.Flush()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReplaceLogger acts as UseLogger but the logger that was previously
|
||||
// used is disposed (except Default and Disabled loggers).
|
||||
//
|
||||
// Example:
|
||||
// import log "github.com/cihub/seelog"
|
||||
//
|
||||
// func main() {
|
||||
// logger, err := log.LoggerFromConfigAsFile("seelog.xml")
|
||||
//
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
//
|
||||
// log.ReplaceLogger(logger)
|
||||
// defer log.Flush()
|
||||
//
|
||||
// log.Trace("test")
|
||||
// log.Debugf("var = %s", "abc")
|
||||
// }
|
||||
func ReplaceLogger(logger LoggerInterface) error {
|
||||
if logger == nil {
|
||||
return errors.New("logger can not be nil")
|
||||
}
|
||||
|
||||
pkgOperationsMutex.Lock()
|
||||
defer pkgOperationsMutex.Unlock()
|
||||
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
reportInternalError(fmt.Errorf("recovered from panic during ReplaceLogger: %s", err))
|
||||
}
|
||||
}()
|
||||
|
||||
if Current == Default {
|
||||
Current.Flush()
|
||||
} else if Current != nil && !Current.Closed() && Current != Disabled {
|
||||
Current.Flush()
|
||||
Current.Close()
|
||||
}
|
||||
|
||||
Current = logger
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Tracef formats message according to format specifier
|
||||
// and writes to default logger with log level = Trace.
|
||||
func Tracef(format string, params ...interface{}) {
|
||||
pkgOperationsMutex.Lock()
|
||||
defer pkgOperationsMutex.Unlock()
|
||||
Current.traceWithCallDepth(staticFuncCallDepth, newLogFormattedMessage(format, params))
|
||||
}
|
||||
|
||||
// Debugf formats message according to format specifier
|
||||
// and writes to default logger with log level = Debug.
|
||||
func Debugf(format string, params ...interface{}) {
|
||||
pkgOperationsMutex.Lock()
|
||||
defer pkgOperationsMutex.Unlock()
|
||||
Current.debugWithCallDepth(staticFuncCallDepth, newLogFormattedMessage(format, params))
|
||||
}
|
||||
|
||||
// Infof formats message according to format specifier
|
||||
// and writes to default logger with log level = Info.
|
||||
func Infof(format string, params ...interface{}) {
|
||||
pkgOperationsMutex.Lock()
|
||||
defer pkgOperationsMutex.Unlock()
|
||||
Current.infoWithCallDepth(staticFuncCallDepth, newLogFormattedMessage(format, params))
|
||||
}
|
||||
|
||||
// Warnf formats message according to format specifier and writes to default logger with log level = Warn
|
||||
func Warnf(format string, params ...interface{}) error {
|
||||
pkgOperationsMutex.Lock()
|
||||
defer pkgOperationsMutex.Unlock()
|
||||
message := newLogFormattedMessage(format, params)
|
||||
Current.warnWithCallDepth(staticFuncCallDepth, message)
|
||||
return errors.New(message.String())
|
||||
}
|
||||
|
||||
// Errorf formats message according to format specifier and writes to default logger with log level = Error
|
||||
func Errorf(format string, params ...interface{}) error {
|
||||
pkgOperationsMutex.Lock()
|
||||
defer pkgOperationsMutex.Unlock()
|
||||
message := newLogFormattedMessage(format, params)
|
||||
Current.errorWithCallDepth(staticFuncCallDepth, message)
|
||||
return errors.New(message.String())
|
||||
}
|
||||
|
||||
// Criticalf formats message according to format specifier and writes to default logger with log level = Critical
|
||||
func Criticalf(format string, params ...interface{}) error {
|
||||
pkgOperationsMutex.Lock()
|
||||
defer pkgOperationsMutex.Unlock()
|
||||
message := newLogFormattedMessage(format, params)
|
||||
Current.criticalWithCallDepth(staticFuncCallDepth, message)
|
||||
return errors.New(message.String())
|
||||
}
|
||||
|
||||
// Trace formats message using the default formats for its operands and writes to default logger with log level = Trace
|
||||
func Trace(v ...interface{}) {
|
||||
pkgOperationsMutex.Lock()
|
||||
defer pkgOperationsMutex.Unlock()
|
||||
Current.traceWithCallDepth(staticFuncCallDepth, newLogMessage(v))
|
||||
}
|
||||
|
||||
// Debug formats message using the default formats for its operands and writes to default logger with log level = Debug
|
||||
func Debug(v ...interface{}) {
|
||||
pkgOperationsMutex.Lock()
|
||||
defer pkgOperationsMutex.Unlock()
|
||||
Current.debugWithCallDepth(staticFuncCallDepth, newLogMessage(v))
|
||||
}
|
||||
|
||||
// Info formats message using the default formats for its operands and writes to default logger with log level = Info
|
||||
func Info(v ...interface{}) {
|
||||
pkgOperationsMutex.Lock()
|
||||
defer pkgOperationsMutex.Unlock()
|
||||
Current.infoWithCallDepth(staticFuncCallDepth, newLogMessage(v))
|
||||
}
|
||||
|
||||
// Warn formats message using the default formats for its operands and writes to default logger with log level = Warn
|
||||
func Warn(v ...interface{}) error {
|
||||
pkgOperationsMutex.Lock()
|
||||
defer pkgOperationsMutex.Unlock()
|
||||
message := newLogMessage(v)
|
||||
Current.warnWithCallDepth(staticFuncCallDepth, message)
|
||||
return errors.New(message.String())
|
||||
}
|
||||
|
||||
// Error formats message using the default formats for its operands and writes to default logger with log level = Error
|
||||
func Error(v ...interface{}) error {
|
||||
pkgOperationsMutex.Lock()
|
||||
defer pkgOperationsMutex.Unlock()
|
||||
message := newLogMessage(v)
|
||||
Current.errorWithCallDepth(staticFuncCallDepth, message)
|
||||
return errors.New(message.String())
|
||||
}
|
||||
|
||||
// Critical formats message using the default formats for its operands and writes to default logger with log level = Critical
|
||||
func Critical(v ...interface{}) error {
|
||||
pkgOperationsMutex.Lock()
|
||||
defer pkgOperationsMutex.Unlock()
|
||||
message := newLogMessage(v)
|
||||
Current.criticalWithCallDepth(staticFuncCallDepth, message)
|
||||
return errors.New(message.String())
|
||||
}
|
||||
|
||||
// Flush immediately processes all currently queued messages and all currently buffered messages.
|
||||
// It is a blocking call which returns only after the queue is empty and all the buffers are empty.
|
||||
//
|
||||
// If Flush is called for a synchronous logger (type='sync'), it only flushes buffers (e.g. '<buffered>' receivers)
|
||||
// , because there is no queue.
|
||||
//
|
||||
// Call this method when your app is going to shut down not to lose any log messages.
|
||||
func Flush() {
|
||||
pkgOperationsMutex.Lock()
|
||||
defer pkgOperationsMutex.Unlock()
|
||||
Current.Flush()
|
||||
}
|
|
@ -0,0 +1,370 @@
|
|||
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||
// list of conditions and the following disclaimer.
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package seelog
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
)
|
||||
|
||||
func reportInternalError(err error) {
|
||||
fmt.Fprintf(os.Stderr, "seelog internal error: %s\n", err)
|
||||
}
|
||||
|
||||
// LoggerInterface represents structs capable of logging Seelog messages
|
||||
type LoggerInterface interface {
|
||||
|
||||
// Tracef formats message according to format specifier
|
||||
// and writes to log with level = Trace.
|
||||
Tracef(format string, params ...interface{})
|
||||
|
||||
// Debugf formats message according to format specifier
|
||||
// and writes to log with level = Debug.
|
||||
Debugf(format string, params ...interface{})
|
||||
|
||||
// Infof formats message according to format specifier
|
||||
// and writes to log with level = Info.
|
||||
Infof(format string, params ...interface{})
|
||||
|
||||
// Warnf formats message according to format specifier
|
||||
// and writes to log with level = Warn.
|
||||
Warnf(format string, params ...interface{}) error
|
||||
|
||||
// Errorf formats message according to format specifier
|
||||
// and writes to log with level = Error.
|
||||
Errorf(format string, params ...interface{}) error
|
||||
|
||||
// Criticalf formats message according to format specifier
|
||||
// and writes to log with level = Critical.
|
||||
Criticalf(format string, params ...interface{}) error
|
||||
|
||||
// Trace formats message using the default formats for its operands
|
||||
// and writes to log with level = Trace
|
||||
Trace(v ...interface{})
|
||||
|
||||
// Debug formats message using the default formats for its operands
|
||||
// and writes to log with level = Debug
|
||||
Debug(v ...interface{})
|
||||
|
||||
// Info formats message using the default formats for its operands
|
||||
// and writes to log with level = Info
|
||||
Info(v ...interface{})
|
||||
|
||||
// Warn formats message using the default formats for its operands
|
||||
// and writes to log with level = Warn
|
||||
Warn(v ...interface{}) error
|
||||
|
||||
// Error formats message using the default formats for its operands
|
||||
// and writes to log with level = Error
|
||||
Error(v ...interface{}) error
|
||||
|
||||
// Critical formats message using the default formats for its operands
|
||||
// and writes to log with level = Critical
|
||||
Critical(v ...interface{}) error
|
||||
|
||||
traceWithCallDepth(callDepth int, message fmt.Stringer)
|
||||
debugWithCallDepth(callDepth int, message fmt.Stringer)
|
||||
infoWithCallDepth(callDepth int, message fmt.Stringer)
|
||||
warnWithCallDepth(callDepth int, message fmt.Stringer)
|
||||
errorWithCallDepth(callDepth int, message fmt.Stringer)
|
||||
criticalWithCallDepth(callDepth int, message fmt.Stringer)
|
||||
|
||||
// Close flushes all the messages in the logger and closes it. It cannot be used after this operation.
|
||||
Close()
|
||||
|
||||
// Flush flushes all the messages in the logger.
|
||||
Flush()
|
||||
|
||||
// Closed returns true if the logger was previously closed.
|
||||
Closed() bool
|
||||
|
||||
// SetAdditionalStackDepth sets the additional number of frames to skip by runtime.Caller
|
||||
// when getting function information needed to print seelog format identifiers such as %Func or %File.
|
||||
//
|
||||
// This func may be used when you wrap seelog funcs and want to print caller info of you own
|
||||
// wrappers instead of seelog func callers. In this case you should set depth = 1. If you then
|
||||
// wrap your wrapper, you should set depth = 2, etc.
|
||||
//
|
||||
// NOTE: Incorrect depth value may lead to errors in runtime.Caller evaluation or incorrect
|
||||
// function/file names in log files. Do not use it if you are not going to wrap seelog funcs.
|
||||
// You may reset the value to default using a SetAdditionalStackDepth(0) call.
|
||||
SetAdditionalStackDepth(depth int) error
|
||||
|
||||
// Sets logger context that can be used in formatter funcs and custom receivers
|
||||
SetContext(context interface{})
|
||||
}
|
||||
|
||||
// innerLoggerInterface is an internal logging interface
|
||||
type innerLoggerInterface interface {
|
||||
innerLog(level LogLevel, context LogContextInterface, message fmt.Stringer)
|
||||
Flush()
|
||||
}
|
||||
|
||||
// [file path][func name][level] -> [allowed]
|
||||
type allowedContextCache map[string]map[string]map[LogLevel]bool
|
||||
|
||||
// commonLogger contains all common data needed for logging and contains methods used to log messages.
|
||||
type commonLogger struct {
|
||||
config *logConfig // Config used for logging
|
||||
contextCache allowedContextCache // Caches whether log is enabled for specific "full path-func name-level" sets
|
||||
closed bool // 'true' when all writers are closed, all data is flushed, logger is unusable. Must be accessed while holding closedM
|
||||
closedM sync.RWMutex
|
||||
m sync.Mutex // Mutex for main operations
|
||||
unusedLevels []bool
|
||||
innerLogger innerLoggerInterface
|
||||
addStackDepth int // Additional stack depth needed for correct seelog caller context detection
|
||||
customContext interface{}
|
||||
}
|
||||
|
||||
func newCommonLogger(config *logConfig, internalLogger innerLoggerInterface) *commonLogger {
|
||||
cLogger := new(commonLogger)
|
||||
|
||||
cLogger.config = config
|
||||
cLogger.contextCache = make(allowedContextCache)
|
||||
cLogger.unusedLevels = make([]bool, Off)
|
||||
cLogger.fillUnusedLevels()
|
||||
cLogger.innerLogger = internalLogger
|
||||
|
||||
return cLogger
|
||||
}
|
||||
|
||||
func (cLogger *commonLogger) SetAdditionalStackDepth(depth int) error {
|
||||
if depth < 0 {
|
||||
return fmt.Errorf("negative depth: %d", depth)
|
||||
}
|
||||
cLogger.m.Lock()
|
||||
cLogger.addStackDepth = depth
|
||||
cLogger.m.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cLogger *commonLogger) Tracef(format string, params ...interface{}) {
|
||||
cLogger.traceWithCallDepth(loggerFuncCallDepth, newLogFormattedMessage(format, params))
|
||||
}
|
||||
|
||||
func (cLogger *commonLogger) Debugf(format string, params ...interface{}) {
|
||||
cLogger.debugWithCallDepth(loggerFuncCallDepth, newLogFormattedMessage(format, params))
|
||||
}
|
||||
|
||||
func (cLogger *commonLogger) Infof(format string, params ...interface{}) {
|
||||
cLogger.infoWithCallDepth(loggerFuncCallDepth, newLogFormattedMessage(format, params))
|
||||
}
|
||||
|
||||
func (cLogger *commonLogger) Warnf(format string, params ...interface{}) error {
|
||||
message := newLogFormattedMessage(format, params)
|
||||
cLogger.warnWithCallDepth(loggerFuncCallDepth, message)
|
||||
return errors.New(message.String())
|
||||
}
|
||||
|
||||
func (cLogger *commonLogger) Errorf(format string, params ...interface{}) error {
|
||||
message := newLogFormattedMessage(format, params)
|
||||
cLogger.errorWithCallDepth(loggerFuncCallDepth, message)
|
||||
return errors.New(message.String())
|
||||
}
|
||||
|
||||
func (cLogger *commonLogger) Criticalf(format string, params ...interface{}) error {
|
||||
message := newLogFormattedMessage(format, params)
|
||||
cLogger.criticalWithCallDepth(loggerFuncCallDepth, message)
|
||||
return errors.New(message.String())
|
||||
}
|
||||
|
||||
func (cLogger *commonLogger) Trace(v ...interface{}) {
|
||||
cLogger.traceWithCallDepth(loggerFuncCallDepth, newLogMessage(v))
|
||||
}
|
||||
|
||||
func (cLogger *commonLogger) Debug(v ...interface{}) {
|
||||
cLogger.debugWithCallDepth(loggerFuncCallDepth, newLogMessage(v))
|
||||
}
|
||||
|
||||
func (cLogger *commonLogger) Info(v ...interface{}) {
|
||||
cLogger.infoWithCallDepth(loggerFuncCallDepth, newLogMessage(v))
|
||||
}
|
||||
|
||||
func (cLogger *commonLogger) Warn(v ...interface{}) error {
|
||||
message := newLogMessage(v)
|
||||
cLogger.warnWithCallDepth(loggerFuncCallDepth, message)
|
||||
return errors.New(message.String())
|
||||
}
|
||||
|
||||
func (cLogger *commonLogger) Error(v ...interface{}) error {
|
||||
message := newLogMessage(v)
|
||||
cLogger.errorWithCallDepth(loggerFuncCallDepth, message)
|
||||
return errors.New(message.String())
|
||||
}
|
||||
|
||||
func (cLogger *commonLogger) Critical(v ...interface{}) error {
|
||||
message := newLogMessage(v)
|
||||
cLogger.criticalWithCallDepth(loggerFuncCallDepth, message)
|
||||
return errors.New(message.String())
|
||||
}
|
||||
|
||||
func (cLogger *commonLogger) SetContext(c interface{}) {
|
||||
cLogger.customContext = c
|
||||
}
|
||||
|
||||
func (cLogger *commonLogger) traceWithCallDepth(callDepth int, message fmt.Stringer) {
|
||||
cLogger.log(TraceLvl, message, callDepth)
|
||||
}
|
||||
|
||||
func (cLogger *commonLogger) debugWithCallDepth(callDepth int, message fmt.Stringer) {
|
||||
cLogger.log(DebugLvl, message, callDepth)
|
||||
}
|
||||
|
||||
func (cLogger *commonLogger) infoWithCallDepth(callDepth int, message fmt.Stringer) {
|
||||
cLogger.log(InfoLvl, message, callDepth)
|
||||
}
|
||||
|
||||
func (cLogger *commonLogger) warnWithCallDepth(callDepth int, message fmt.Stringer) {
|
||||
cLogger.log(WarnLvl, message, callDepth)
|
||||
}
|
||||
|
||||
func (cLogger *commonLogger) errorWithCallDepth(callDepth int, message fmt.Stringer) {
|
||||
cLogger.log(ErrorLvl, message, callDepth)
|
||||
}
|
||||
|
||||
func (cLogger *commonLogger) criticalWithCallDepth(callDepth int, message fmt.Stringer) {
|
||||
cLogger.log(CriticalLvl, message, callDepth)
|
||||
cLogger.innerLogger.Flush()
|
||||
}
|
||||
|
||||
func (cLogger *commonLogger) Closed() bool {
|
||||
cLogger.closedM.RLock()
|
||||
defer cLogger.closedM.RUnlock()
|
||||
return cLogger.closed
|
||||
}
|
||||
|
||||
func (cLogger *commonLogger) fillUnusedLevels() {
|
||||
for i := 0; i < len(cLogger.unusedLevels); i++ {
|
||||
cLogger.unusedLevels[i] = true
|
||||
}
|
||||
|
||||
cLogger.fillUnusedLevelsByContraint(cLogger.config.Constraints)
|
||||
|
||||
for _, exception := range cLogger.config.Exceptions {
|
||||
cLogger.fillUnusedLevelsByContraint(exception)
|
||||
}
|
||||
}
|
||||
|
||||
func (cLogger *commonLogger) fillUnusedLevelsByContraint(constraint logLevelConstraints) {
|
||||
for i := 0; i < len(cLogger.unusedLevels); i++ {
|
||||
if constraint.IsAllowed(LogLevel(i)) {
|
||||
cLogger.unusedLevels[i] = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// stackCallDepth is used to indicate the call depth of 'log' func.
|
||||
// This depth level is used in the runtime.Caller(...) call. See
|
||||
// common_context.go -> specifyContext, extractCallerInfo for details.
|
||||
func (cLogger *commonLogger) log(level LogLevel, message fmt.Stringer, stackCallDepth int) {
|
||||
if cLogger.unusedLevels[level] {
|
||||
return
|
||||
}
|
||||
cLogger.m.Lock()
|
||||
defer cLogger.m.Unlock()
|
||||
|
||||
if cLogger.Closed() {
|
||||
return
|
||||
}
|
||||
context, _ := specifyContext(stackCallDepth+cLogger.addStackDepth, cLogger.customContext)
|
||||
// Context errors are not reported because there are situations
|
||||
// in which context errors are normal Seelog usage cases. For
|
||||
// example in executables with stripped symbols.
|
||||
// Error contexts are returned instead. See common_context.go.
|
||||
/*if err != nil {
|
||||
reportInternalError(err)
|
||||
return
|
||||
}*/
|
||||
cLogger.innerLogger.innerLog(level, context, message)
|
||||
}
|
||||
|
||||
func (cLogger *commonLogger) processLogMsg(level LogLevel, message fmt.Stringer, context LogContextInterface) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
reportInternalError(fmt.Errorf("recovered from panic during message processing: %s", err))
|
||||
}
|
||||
}()
|
||||
if cLogger.config.IsAllowed(level, context) {
|
||||
cLogger.config.RootDispatcher.Dispatch(message.String(), level, context, reportInternalError)
|
||||
}
|
||||
}
|
||||
|
||||
func (cLogger *commonLogger) isAllowed(level LogLevel, context LogContextInterface) bool {
|
||||
funcMap, ok := cLogger.contextCache[context.FullPath()]
|
||||
if !ok {
|
||||
funcMap = make(map[string]map[LogLevel]bool, 0)
|
||||
cLogger.contextCache[context.FullPath()] = funcMap
|
||||
}
|
||||
|
||||
levelMap, ok := funcMap[context.Func()]
|
||||
if !ok {
|
||||
levelMap = make(map[LogLevel]bool, 0)
|
||||
funcMap[context.Func()] = levelMap
|
||||
}
|
||||
|
||||
isAllowValue, ok := levelMap[level]
|
||||
if !ok {
|
||||
isAllowValue = cLogger.config.IsAllowed(level, context)
|
||||
levelMap[level] = isAllowValue
|
||||
}
|
||||
|
||||
return isAllowValue
|
||||
}
|
||||
|
||||
type logMessage struct {
|
||||
params []interface{}
|
||||
}
|
||||
|
||||
type logFormattedMessage struct {
|
||||
format string
|
||||
params []interface{}
|
||||
}
|
||||
|
||||
func newLogMessage(params []interface{}) fmt.Stringer {
|
||||
message := new(logMessage)
|
||||
|
||||
message.params = params
|
||||
|
||||
return message
|
||||
}
|
||||
|
||||
func newLogFormattedMessage(format string, params []interface{}) *logFormattedMessage {
|
||||
message := new(logFormattedMessage)
|
||||
|
||||
message.params = params
|
||||
message.format = format
|
||||
|
||||
return message
|
||||
}
|
||||
|
||||
func (message *logMessage) String() string {
|
||||
return fmt.Sprint(message.params...)
|
||||
}
|
||||
|
||||
func (message *logFormattedMessage) String() string {
|
||||
return fmt.Sprintf(message.format, message.params...)
|
||||
}
|
|
@ -0,0 +1,161 @@
|
|||
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||
// list of conditions and the following disclaimer.
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package seelog
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// bufferedWriter stores data in memory and flushes it every flushPeriod or when buffer is full
|
||||
type bufferedWriter struct {
|
||||
flushPeriod time.Duration // data flushes interval (in microseconds)
|
||||
bufferMutex *sync.Mutex // mutex for buffer operations syncronization
|
||||
innerWriter io.Writer // inner writer
|
||||
buffer *bufio.Writer // buffered wrapper for inner writer
|
||||
bufferSize int // max size of data chunk in bytes
|
||||
}
|
||||
|
||||
// NewBufferedWriter creates a new buffered writer struct.
|
||||
// bufferSize -- size of memory buffer in bytes
|
||||
// flushPeriod -- period in which data flushes from memory buffer in milliseconds. 0 - turn off this functionality
|
||||
func NewBufferedWriter(innerWriter io.Writer, bufferSize int, flushPeriod time.Duration) (*bufferedWriter, error) {
|
||||
|
||||
if innerWriter == nil {
|
||||
return nil, errors.New("argument is nil: innerWriter")
|
||||
}
|
||||
if flushPeriod < 0 {
|
||||
return nil, fmt.Errorf("flushPeriod can not be less than 0. Got: %d", flushPeriod)
|
||||
}
|
||||
|
||||
if bufferSize <= 0 {
|
||||
return nil, fmt.Errorf("bufferSize can not be less or equal to 0. Got: %d", bufferSize)
|
||||
}
|
||||
|
||||
buffer := bufio.NewWriterSize(innerWriter, bufferSize)
|
||||
|
||||
/*if err != nil {
|
||||
return nil, err
|
||||
}*/
|
||||
|
||||
newWriter := new(bufferedWriter)
|
||||
|
||||
newWriter.innerWriter = innerWriter
|
||||
newWriter.buffer = buffer
|
||||
newWriter.bufferSize = bufferSize
|
||||
newWriter.flushPeriod = flushPeriod * 1e6
|
||||
newWriter.bufferMutex = new(sync.Mutex)
|
||||
|
||||
if flushPeriod != 0 {
|
||||
go newWriter.flushPeriodically()
|
||||
}
|
||||
|
||||
return newWriter, nil
|
||||
}
|
||||
|
||||
func (bufWriter *bufferedWriter) writeBigChunk(bytes []byte) (n int, err error) {
|
||||
bufferedLen := bufWriter.buffer.Buffered()
|
||||
|
||||
n, err = bufWriter.flushInner()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
written, writeErr := bufWriter.innerWriter.Write(bytes)
|
||||
return bufferedLen + written, writeErr
|
||||
}
|
||||
|
||||
// Sends data to buffer manager. Waits until all buffers are full.
|
||||
func (bufWriter *bufferedWriter) Write(bytes []byte) (n int, err error) {
|
||||
|
||||
bufWriter.bufferMutex.Lock()
|
||||
defer bufWriter.bufferMutex.Unlock()
|
||||
|
||||
bytesLen := len(bytes)
|
||||
|
||||
if bytesLen > bufWriter.bufferSize {
|
||||
return bufWriter.writeBigChunk(bytes)
|
||||
}
|
||||
|
||||
if bytesLen > bufWriter.buffer.Available() {
|
||||
n, err = bufWriter.flushInner()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
bufWriter.buffer.Write(bytes)
|
||||
|
||||
return len(bytes), nil
|
||||
}
|
||||
|
||||
func (bufWriter *bufferedWriter) Close() error {
|
||||
closer, ok := bufWriter.innerWriter.(io.Closer)
|
||||
if ok {
|
||||
return closer.Close()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bufWriter *bufferedWriter) Flush() {
|
||||
|
||||
bufWriter.bufferMutex.Lock()
|
||||
defer bufWriter.bufferMutex.Unlock()
|
||||
|
||||
bufWriter.flushInner()
|
||||
}
|
||||
|
||||
func (bufWriter *bufferedWriter) flushInner() (n int, err error) {
|
||||
bufferedLen := bufWriter.buffer.Buffered()
|
||||
flushErr := bufWriter.buffer.Flush()
|
||||
|
||||
return bufWriter.buffer.Buffered() - bufferedLen, flushErr
|
||||
}
|
||||
|
||||
func (bufWriter *bufferedWriter) flushBuffer() {
|
||||
bufWriter.bufferMutex.Lock()
|
||||
defer bufWriter.bufferMutex.Unlock()
|
||||
|
||||
bufWriter.buffer.Flush()
|
||||
}
|
||||
|
||||
func (bufWriter *bufferedWriter) flushPeriodically() {
|
||||
if bufWriter.flushPeriod > 0 {
|
||||
ticker := time.NewTicker(bufWriter.flushPeriod)
|
||||
for {
|
||||
<-ticker.C
|
||||
bufWriter.flushBuffer()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (bufWriter *bufferedWriter) String() string {
|
||||
return fmt.Sprintf("bufferedWriter size: %d, flushPeriod: %d", bufWriter.bufferSize, bufWriter.flushPeriod)
|
||||
}
|
|
@ -0,0 +1,144 @@
|
|||
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||
// list of conditions and the following disclaimer.
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package seelog
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
)
|
||||
|
||||
// connWriter is used to write to a stream-oriented network connection.
|
||||
type connWriter struct {
|
||||
innerWriter io.WriteCloser
|
||||
reconnectOnMsg bool
|
||||
reconnect bool
|
||||
net string
|
||||
addr string
|
||||
useTLS bool
|
||||
configTLS *tls.Config
|
||||
}
|
||||
|
||||
// Creates writer to the address addr on the network netName.
|
||||
// Connection will be opened on each write if reconnectOnMsg = true
|
||||
func NewConnWriter(netName string, addr string, reconnectOnMsg bool) *connWriter {
|
||||
newWriter := new(connWriter)
|
||||
|
||||
newWriter.net = netName
|
||||
newWriter.addr = addr
|
||||
newWriter.reconnectOnMsg = reconnectOnMsg
|
||||
|
||||
return newWriter
|
||||
}
|
||||
|
||||
// Creates a writer that uses SSL/TLS
|
||||
func newTLSWriter(netName string, addr string, reconnectOnMsg bool, config *tls.Config) *connWriter {
|
||||
newWriter := new(connWriter)
|
||||
|
||||
newWriter.net = netName
|
||||
newWriter.addr = addr
|
||||
newWriter.reconnectOnMsg = reconnectOnMsg
|
||||
newWriter.useTLS = true
|
||||
newWriter.configTLS = config
|
||||
|
||||
return newWriter
|
||||
}
|
||||
|
||||
func (connWriter *connWriter) Close() error {
|
||||
if connWriter.innerWriter == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return connWriter.innerWriter.Close()
|
||||
}
|
||||
|
||||
func (connWriter *connWriter) Write(bytes []byte) (n int, err error) {
|
||||
if connWriter.neededConnectOnMsg() {
|
||||
err = connWriter.connect()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
if connWriter.reconnectOnMsg {
|
||||
defer connWriter.innerWriter.Close()
|
||||
}
|
||||
|
||||
n, err = connWriter.innerWriter.Write(bytes)
|
||||
if err != nil {
|
||||
connWriter.reconnect = true
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (connWriter *connWriter) String() string {
|
||||
return fmt.Sprintf("Conn writer: [%s, %s, %v]", connWriter.net, connWriter.addr, connWriter.reconnectOnMsg)
|
||||
}
|
||||
|
||||
func (connWriter *connWriter) connect() error {
|
||||
if connWriter.innerWriter != nil {
|
||||
connWriter.innerWriter.Close()
|
||||
connWriter.innerWriter = nil
|
||||
}
|
||||
|
||||
if connWriter.useTLS {
|
||||
conn, err := tls.Dial(connWriter.net, connWriter.addr, connWriter.configTLS)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
connWriter.innerWriter = conn
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
conn, err := net.Dial(connWriter.net, connWriter.addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tcpConn, ok := conn.(*net.TCPConn)
|
||||
if ok {
|
||||
tcpConn.SetKeepAlive(true)
|
||||
}
|
||||
|
||||
connWriter.innerWriter = conn
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (connWriter *connWriter) neededConnectOnMsg() bool {
|
||||
if connWriter.reconnect {
|
||||
connWriter.reconnect = false
|
||||
return true
|
||||
}
|
||||
|
||||
if connWriter.innerWriter == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
return connWriter.reconnectOnMsg
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||
// list of conditions and the following disclaimer.
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package seelog
|
||||
|
||||
import "fmt"
|
||||
|
||||
// consoleWriter is used to write to console
|
||||
type consoleWriter struct {
|
||||
}
|
||||
|
||||
// Creates a new console writer. Returns error, if the console writer couldn't be created.
|
||||
func NewConsoleWriter() (writer *consoleWriter, err error) {
|
||||
newWriter := new(consoleWriter)
|
||||
|
||||
return newWriter, nil
|
||||
}
|
||||
|
||||
// Create folder and file on WriteLog/Write first call
|
||||
func (console *consoleWriter) Write(bytes []byte) (int, error) {
|
||||
return fmt.Print(string(bytes))
|
||||
}
|
||||
|
||||
func (console *consoleWriter) String() string {
|
||||
return "Console writer"
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||
// list of conditions and the following disclaimer.
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package seelog
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// fileWriter is used to write to a file.
|
||||
type fileWriter struct {
|
||||
innerWriter io.WriteCloser
|
||||
fileName string
|
||||
}
|
||||
|
||||
// Creates a new file and a corresponding writer. Returns error, if the file couldn't be created.
|
||||
func NewFileWriter(fileName string) (writer *fileWriter, err error) {
|
||||
newWriter := new(fileWriter)
|
||||
newWriter.fileName = fileName
|
||||
|
||||
return newWriter, nil
|
||||
}
|
||||
|
||||
func (fw *fileWriter) Close() error {
|
||||
if fw.innerWriter != nil {
|
||||
err := fw.innerWriter.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fw.innerWriter = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create folder and file on WriteLog/Write first call
|
||||
func (fw *fileWriter) Write(bytes []byte) (n int, err error) {
|
||||
if fw.innerWriter == nil {
|
||||
if err := fw.createFile(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
return fw.innerWriter.Write(bytes)
|
||||
}
|
||||
|
||||
func (fw *fileWriter) createFile() error {
|
||||
folder, _ := filepath.Split(fw.fileName)
|
||||
var err error
|
||||
|
||||
if 0 != len(folder) {
|
||||
err = os.MkdirAll(folder, defaultDirectoryPermissions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// If exists
|
||||
fw.innerWriter, err = os.OpenFile(fw.fileName, os.O_WRONLY|os.O_APPEND|os.O_CREATE, defaultFilePermissions)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fw *fileWriter) String() string {
|
||||
return fmt.Sprintf("File writer: %s", fw.fileName)
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||
// list of conditions and the following disclaimer.
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package seelog
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
type formattedWriter struct {
|
||||
writer io.Writer
|
||||
formatter *formatter
|
||||
}
|
||||
|
||||
func NewFormattedWriter(writer io.Writer, formatter *formatter) (*formattedWriter, error) {
|
||||
if formatter == nil {
|
||||
return nil, errors.New("formatter can not be nil")
|
||||
}
|
||||
|
||||
return &formattedWriter{writer, formatter}, nil
|
||||
}
|
||||
|
||||
func (formattedWriter *formattedWriter) Write(message string, level LogLevel, context LogContextInterface) error {
|
||||
str := formattedWriter.formatter.Format(message, level, context)
|
||||
_, err := formattedWriter.writer.Write([]byte(str))
|
||||
return err
|
||||
}
|
||||
|
||||
func (formattedWriter *formattedWriter) String() string {
|
||||
return fmt.Sprintf("writer: %s, format: %s", formattedWriter.writer, formattedWriter.formatter)
|
||||
}
|
||||
|
||||
func (formattedWriter *formattedWriter) Writer() io.Writer {
|
||||
return formattedWriter.writer
|
||||
}
|
||||
|
||||
func (formattedWriter *formattedWriter) Format() *formatter {
|
||||
return formattedWriter.formatter
|
||||
}
|
|
@ -0,0 +1,625 @@
|
|||
// Copyright (c) 2013 - Cloud Instruments Co., Ltd.
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||
// list of conditions and the following disclaimer.
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package seelog
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Common constants
|
||||
const (
|
||||
rollingLogHistoryDelimiter = "."
|
||||
)
|
||||
|
||||
// Types of the rolling writer: roll by date, by time, etc.
|
||||
type rollingType uint8
|
||||
|
||||
const (
|
||||
rollingTypeSize = iota
|
||||
rollingTypeTime
|
||||
)
|
||||
|
||||
// Types of the rolled file naming mode: prefix, postfix, etc.
|
||||
type rollingNameMode uint8
|
||||
|
||||
const (
|
||||
rollingNameModePostfix = iota
|
||||
rollingNameModePrefix
|
||||
)
|
||||
|
||||
var rollingNameModesStringRepresentation = map[rollingNameMode]string{
|
||||
rollingNameModePostfix: "postfix",
|
||||
rollingNameModePrefix: "prefix",
|
||||
}
|
||||
|
||||
func rollingNameModeFromString(rollingNameStr string) (rollingNameMode, bool) {
|
||||
for tp, tpStr := range rollingNameModesStringRepresentation {
|
||||
if tpStr == rollingNameStr {
|
||||
return tp, true
|
||||
}
|
||||
}
|
||||
|
||||
return 0, false
|
||||
}
|
||||
|
||||
type rollingIntervalType uint8
|
||||
|
||||
const (
|
||||
rollingIntervalAny = iota
|
||||
rollingIntervalDaily
|
||||
)
|
||||
|
||||
var rollingInvervalTypesStringRepresentation = map[rollingIntervalType]string{
|
||||
rollingIntervalDaily: "daily",
|
||||
}
|
||||
|
||||
func rollingIntervalTypeFromString(rollingTypeStr string) (rollingIntervalType, bool) {
|
||||
for tp, tpStr := range rollingInvervalTypesStringRepresentation {
|
||||
if tpStr == rollingTypeStr {
|
||||
return tp, true
|
||||
}
|
||||
}
|
||||
|
||||
return 0, false
|
||||
}
|
||||
|
||||
var rollingTypesStringRepresentation = map[rollingType]string{
|
||||
rollingTypeSize: "size",
|
||||
rollingTypeTime: "date",
|
||||
}
|
||||
|
||||
func rollingTypeFromString(rollingTypeStr string) (rollingType, bool) {
|
||||
for tp, tpStr := range rollingTypesStringRepresentation {
|
||||
if tpStr == rollingTypeStr {
|
||||
return tp, true
|
||||
}
|
||||
}
|
||||
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// Old logs archivation type.
|
||||
type rollingArchiveType uint8
|
||||
|
||||
const (
|
||||
rollingArchiveNone = iota
|
||||
rollingArchiveZip
|
||||
)
|
||||
|
||||
var rollingArchiveTypesStringRepresentation = map[rollingArchiveType]string{
|
||||
rollingArchiveNone: "none",
|
||||
rollingArchiveZip: "zip",
|
||||
}
|
||||
|
||||
func rollingArchiveTypeFromString(rollingArchiveTypeStr string) (rollingArchiveType, bool) {
|
||||
for tp, tpStr := range rollingArchiveTypesStringRepresentation {
|
||||
if tpStr == rollingArchiveTypeStr {
|
||||
return tp, true
|
||||
}
|
||||
}
|
||||
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// Default names for different archivation types
|
||||
var rollingArchiveTypesDefaultNames = map[rollingArchiveType]string{
|
||||
rollingArchiveZip: "log.zip",
|
||||
}
|
||||
|
||||
// rollerVirtual is an interface that represents all virtual funcs that are
|
||||
// called in different rolling writer subtypes.
|
||||
type rollerVirtual interface {
|
||||
needsToRoll() (bool, error) // Returns true if needs to switch to another file.
|
||||
isFileRollNameValid(rname string) bool // Returns true if logger roll file name (postfix/prefix/etc.) is ok.
|
||||
sortFileRollNamesAsc(fs []string) ([]string, error) // Sorts logger roll file names in ascending order of their creation by logger.
|
||||
|
||||
// Creates a new froll history file using the contents of current file and special filename of the latest roll (prefix/ postfix).
|
||||
// If lastRollName is empty (""), then it means that there is no latest roll (current is the first one)
|
||||
getNewHistoryRollFileName(lastRollName string) string
|
||||
getCurrentModifiedFileName(originalFileName string, first bool) (string, error) // Returns filename modified according to specific logger rules
|
||||
}
|
||||
|
||||
// rollingFileWriter writes received messages to a file, until time interval passes
|
||||
// or file exceeds a specified limit. After that the current log file is renamed
|
||||
// and writer starts to log into a new file. You can set a limit for such renamed
|
||||
// files count, if you want, and then the rolling writer would delete older ones when
|
||||
// the files count exceed the specified limit.
|
||||
type rollingFileWriter struct {
|
||||
fileName string // current file name. May differ from original in date rolling loggers
|
||||
originalFileName string // original one
|
||||
currentDirPath string
|
||||
currentFile *os.File
|
||||
currentFileSize int64
|
||||
rollingType rollingType // Rolling mode (Files roll by size/date/...)
|
||||
archiveType rollingArchiveType
|
||||
archivePath string
|
||||
maxRolls int
|
||||
nameMode rollingNameMode
|
||||
self rollerVirtual // Used for virtual calls
|
||||
}
|
||||
|
||||
func newRollingFileWriter(fpath string, rtype rollingType, atype rollingArchiveType, apath string, maxr int, namemode rollingNameMode) (*rollingFileWriter, error) {
|
||||
rw := new(rollingFileWriter)
|
||||
rw.currentDirPath, rw.fileName = filepath.Split(fpath)
|
||||
if len(rw.currentDirPath) == 0 {
|
||||
rw.currentDirPath = "."
|
||||
}
|
||||
rw.originalFileName = rw.fileName
|
||||
|
||||
rw.rollingType = rtype
|
||||
rw.archiveType = atype
|
||||
rw.archivePath = apath
|
||||
rw.nameMode = namemode
|
||||
rw.maxRolls = maxr
|
||||
return rw, nil
|
||||
}
|
||||
|
||||
func (rw *rollingFileWriter) hasRollName(file string) bool {
|
||||
switch rw.nameMode {
|
||||
case rollingNameModePostfix:
|
||||
rname := rw.originalFileName + rollingLogHistoryDelimiter
|
||||
return strings.HasPrefix(file, rname)
|
||||
case rollingNameModePrefix:
|
||||
rname := rollingLogHistoryDelimiter + rw.originalFileName
|
||||
return strings.HasSuffix(file, rname)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (rw *rollingFileWriter) createFullFileName(originalName, rollname string) string {
|
||||
switch rw.nameMode {
|
||||
case rollingNameModePostfix:
|
||||
return originalName + rollingLogHistoryDelimiter + rollname
|
||||
case rollingNameModePrefix:
|
||||
return rollname + rollingLogHistoryDelimiter + originalName
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (rw *rollingFileWriter) getSortedLogHistory() ([]string, error) {
|
||||
files, err := getDirFilePaths(rw.currentDirPath, nil, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var validRollNames []string
|
||||
for _, file := range files {
|
||||
if file != rw.fileName && rw.hasRollName(file) {
|
||||
rname := rw.getFileRollName(file)
|
||||
if rw.self.isFileRollNameValid(rname) {
|
||||
validRollNames = append(validRollNames, rname)
|
||||
}
|
||||
}
|
||||
}
|
||||
sortedTails, err := rw.self.sortFileRollNamesAsc(validRollNames)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
validSortedFiles := make([]string, len(sortedTails))
|
||||
for i, v := range sortedTails {
|
||||
validSortedFiles[i] = rw.createFullFileName(rw.originalFileName, v)
|
||||
}
|
||||
return validSortedFiles, nil
|
||||
}
|
||||
|
||||
func (rw *rollingFileWriter) createFileAndFolderIfNeeded(first bool) error {
|
||||
var err error
|
||||
|
||||
if len(rw.currentDirPath) != 0 {
|
||||
err = os.MkdirAll(rw.currentDirPath, defaultDirectoryPermissions)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
rw.fileName, err = rw.self.getCurrentModifiedFileName(rw.originalFileName, first)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
filePath := filepath.Join(rw.currentDirPath, rw.fileName)
|
||||
|
||||
// If exists
|
||||
stat, err := os.Lstat(filePath)
|
||||
if err == nil {
|
||||
rw.currentFile, err = os.OpenFile(filePath, os.O_WRONLY|os.O_APPEND, defaultFilePermissions)
|
||||
|
||||
stat, err = os.Lstat(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rw.currentFileSize = stat.Size()
|
||||
} else {
|
||||
rw.currentFile, err = os.Create(filePath)
|
||||
rw.currentFileSize = 0
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rw *rollingFileWriter) deleteOldRolls(history []string) error {
|
||||
if rw.maxRolls <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
rollsToDelete := len(history) - rw.maxRolls
|
||||
if rollsToDelete <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch rw.archiveType {
|
||||
case rollingArchiveZip:
|
||||
var files map[string][]byte
|
||||
|
||||
// If archive exists
|
||||
_, err := os.Lstat(rw.archivePath)
|
||||
if nil == err {
|
||||
// Extract files and content from it
|
||||
files, err = unzip(rw.archivePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove the original file
|
||||
err = tryRemoveFile(rw.archivePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
files = make(map[string][]byte)
|
||||
}
|
||||
|
||||
// Add files to the existing files map, filled above
|
||||
for i := 0; i < rollsToDelete; i++ {
|
||||
rollPath := filepath.Join(rw.currentDirPath, history[i])
|
||||
bts, err := ioutil.ReadFile(rollPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
files[rollPath] = bts
|
||||
}
|
||||
|
||||
// Put the final file set to zip file.
|
||||
if err = createZip(rw.archivePath, files); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
var err error
|
||||
// In all cases (archive files or not) the files should be deleted.
|
||||
for i := 0; i < rollsToDelete; i++ {
|
||||
// Try best to delete files without breaking the loop.
|
||||
if err = tryRemoveFile(filepath.Join(rw.currentDirPath, history[i])); err != nil {
|
||||
reportInternalError(err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rw *rollingFileWriter) getFileRollName(fileName string) string {
|
||||
switch rw.nameMode {
|
||||
case rollingNameModePostfix:
|
||||
return fileName[len(rw.originalFileName+rollingLogHistoryDelimiter):]
|
||||
case rollingNameModePrefix:
|
||||
return fileName[:len(fileName)-len(rw.originalFileName+rollingLogHistoryDelimiter)]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (rw *rollingFileWriter) Write(bytes []byte) (n int, err error) {
|
||||
if rw.currentFile == nil {
|
||||
err := rw.createFileAndFolderIfNeeded(true)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
// needs to roll if:
|
||||
// * file roller max file size exceeded OR
|
||||
// * time roller interval passed
|
||||
nr, err := rw.self.needsToRoll()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if nr {
|
||||
// First, close current file.
|
||||
err = rw.currentFile.Close()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
// Current history of all previous log files.
|
||||
// For file roller it may be like this:
|
||||
// * ...
|
||||
// * file.log.4
|
||||
// * file.log.5
|
||||
// * file.log.6
|
||||
//
|
||||
// For date roller it may look like this:
|
||||
// * ...
|
||||
// * file.log.11.Aug.13
|
||||
// * file.log.15.Aug.13
|
||||
// * file.log.16.Aug.13
|
||||
// Sorted log history does NOT include current file.
|
||||
history, err := rw.getSortedLogHistory()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
// Renames current file to create a new roll history entry
|
||||
// For file roller it may be like this:
|
||||
// * ...
|
||||
// * file.log.4
|
||||
// * file.log.5
|
||||
// * file.log.6
|
||||
// n file.log.7 <---- RENAMED (from file.log)
|
||||
// Time rollers that doesn't modify file names (e.g. 'date' roller) skip this logic.
|
||||
var newHistoryName string
|
||||
var newRollMarkerName string
|
||||
if len(history) > 0 {
|
||||
// Create new rname name using last history file name
|
||||
newRollMarkerName = rw.self.getNewHistoryRollFileName(rw.getFileRollName(history[len(history)-1]))
|
||||
} else {
|
||||
// Create first rname name
|
||||
newRollMarkerName = rw.self.getNewHistoryRollFileName("")
|
||||
}
|
||||
if len(newRollMarkerName) != 0 {
|
||||
newHistoryName = rw.createFullFileName(rw.fileName, newRollMarkerName)
|
||||
} else {
|
||||
newHistoryName = rw.fileName
|
||||
}
|
||||
if newHistoryName != rw.fileName {
|
||||
err = os.Rename(filepath.Join(rw.currentDirPath, rw.fileName), filepath.Join(rw.currentDirPath, newHistoryName))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
// Finally, add the newly added history file to the history archive
|
||||
// and, if after that the archive exceeds the allowed max limit, older rolls
|
||||
// must the removed/archived.
|
||||
history = append(history, newHistoryName)
|
||||
if len(history) > rw.maxRolls {
|
||||
err = rw.deleteOldRolls(history)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
err = rw.createFileAndFolderIfNeeded(false)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
rw.currentFileSize += int64(len(bytes))
|
||||
return rw.currentFile.Write(bytes)
|
||||
}
|
||||
|
||||
func (rw *rollingFileWriter) Close() error {
|
||||
if rw.currentFile != nil {
|
||||
e := rw.currentFile.Close()
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
rw.currentFile = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// =============================================================================================
|
||||
// Different types of rolling writers
|
||||
// =============================================================================================
|
||||
|
||||
// --------------------------------------------------
|
||||
// Rolling writer by SIZE
|
||||
// --------------------------------------------------
|
||||
|
||||
// rollingFileWriterSize performs roll when file exceeds a specified limit.
|
||||
type rollingFileWriterSize struct {
|
||||
*rollingFileWriter
|
||||
maxFileSize int64
|
||||
}
|
||||
|
||||
func NewRollingFileWriterSize(fpath string, atype rollingArchiveType, apath string, maxSize int64, maxRolls int, namemode rollingNameMode) (*rollingFileWriterSize, error) {
|
||||
rw, err := newRollingFileWriter(fpath, rollingTypeSize, atype, apath, maxRolls, namemode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rws := &rollingFileWriterSize{rw, maxSize}
|
||||
rws.self = rws
|
||||
return rws, nil
|
||||
}
|
||||
|
||||
func (rws *rollingFileWriterSize) needsToRoll() (bool, error) {
|
||||
return rws.currentFileSize >= rws.maxFileSize, nil
|
||||
}
|
||||
|
||||
func (rws *rollingFileWriterSize) isFileRollNameValid(rname string) bool {
|
||||
if len(rname) == 0 {
|
||||
return false
|
||||
}
|
||||
_, err := strconv.Atoi(rname)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
type rollSizeFileTailsSlice []string
|
||||
|
||||
func (p rollSizeFileTailsSlice) Len() int { return len(p) }
|
||||
func (p rollSizeFileTailsSlice) Less(i, j int) bool {
|
||||
v1, _ := strconv.Atoi(p[i])
|
||||
v2, _ := strconv.Atoi(p[j])
|
||||
return v1 < v2
|
||||
}
|
||||
func (p rollSizeFileTailsSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
||||
|
||||
func (rws *rollingFileWriterSize) sortFileRollNamesAsc(fs []string) ([]string, error) {
|
||||
ss := rollSizeFileTailsSlice(fs)
|
||||
sort.Sort(ss)
|
||||
return ss, nil
|
||||
}
|
||||
|
||||
func (rws *rollingFileWriterSize) getNewHistoryRollFileName(lastRollName string) string {
|
||||
v := 0
|
||||
if len(lastRollName) != 0 {
|
||||
v, _ = strconv.Atoi(lastRollName)
|
||||
}
|
||||
return fmt.Sprintf("%d", v+1)
|
||||
}
|
||||
|
||||
func (rws *rollingFileWriterSize) getCurrentModifiedFileName(originalFileName string, first bool) (string, error) {
|
||||
return originalFileName, nil
|
||||
}
|
||||
|
||||
func (rws *rollingFileWriterSize) String() string {
|
||||
return fmt.Sprintf("Rolling file writer (By SIZE): filename: %s, archive: %s, archivefile: %s, maxFileSize: %v, maxRolls: %v",
|
||||
rws.fileName,
|
||||
rollingArchiveTypesStringRepresentation[rws.archiveType],
|
||||
rws.archivePath,
|
||||
rws.maxFileSize,
|
||||
rws.maxRolls)
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
// Rolling writer by TIME
|
||||
// --------------------------------------------------
|
||||
|
||||
// rollingFileWriterTime performs roll when a specified time interval has passed.
|
||||
type rollingFileWriterTime struct {
|
||||
*rollingFileWriter
|
||||
timePattern string
|
||||
interval rollingIntervalType
|
||||
currentTimeFileName string
|
||||
}
|
||||
|
||||
func NewRollingFileWriterTime(fpath string, atype rollingArchiveType, apath string, maxr int,
|
||||
timePattern string, interval rollingIntervalType, namemode rollingNameMode) (*rollingFileWriterTime, error) {
|
||||
|
||||
rw, err := newRollingFileWriter(fpath, rollingTypeTime, atype, apath, maxr, namemode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rws := &rollingFileWriterTime{rw, timePattern, interval, ""}
|
||||
rws.self = rws
|
||||
return rws, nil
|
||||
}
|
||||
|
||||
func (rwt *rollingFileWriterTime) needsToRoll() (bool, error) {
|
||||
switch rwt.nameMode {
|
||||
case rollingNameModePostfix:
|
||||
if rwt.originalFileName+rollingLogHistoryDelimiter+time.Now().Format(rwt.timePattern) == rwt.fileName {
|
||||
return false, nil
|
||||
}
|
||||
case rollingNameModePrefix:
|
||||
if time.Now().Format(rwt.timePattern)+rollingLogHistoryDelimiter+rwt.originalFileName == rwt.fileName {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
if rwt.interval == rollingIntervalAny {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
tprev, err := time.ParseInLocation(rwt.timePattern, rwt.getFileRollName(rwt.fileName), time.Local)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
diff := time.Now().Sub(tprev)
|
||||
switch rwt.interval {
|
||||
case rollingIntervalDaily:
|
||||
return diff >= 24*time.Hour, nil
|
||||
}
|
||||
return false, fmt.Errorf("unknown interval type: %d", rwt.interval)
|
||||
}
|
||||
|
||||
func (rwt *rollingFileWriterTime) isFileRollNameValid(rname string) bool {
|
||||
if len(rname) == 0 {
|
||||
return false
|
||||
}
|
||||
_, err := time.ParseInLocation(rwt.timePattern, rname, time.Local)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
type rollTimeFileTailsSlice struct {
|
||||
data []string
|
||||
pattern string
|
||||
}
|
||||
|
||||
func (p rollTimeFileTailsSlice) Len() int { return len(p.data) }
|
||||
|
||||
func (p rollTimeFileTailsSlice) Less(i, j int) bool {
|
||||
t1, _ := time.ParseInLocation(p.pattern, p.data[i], time.Local)
|
||||
t2, _ := time.ParseInLocation(p.pattern, p.data[j], time.Local)
|
||||
return t1.Before(t2)
|
||||
}
|
||||
|
||||
func (p rollTimeFileTailsSlice) Swap(i, j int) { p.data[i], p.data[j] = p.data[j], p.data[i] }
|
||||
|
||||
func (rwt *rollingFileWriterTime) sortFileRollNamesAsc(fs []string) ([]string, error) {
|
||||
ss := rollTimeFileTailsSlice{data: fs, pattern: rwt.timePattern}
|
||||
sort.Sort(ss)
|
||||
return ss.data, nil
|
||||
}
|
||||
|
||||
func (rwt *rollingFileWriterTime) getNewHistoryRollFileName(lastRollName string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (rwt *rollingFileWriterTime) getCurrentModifiedFileName(originalFileName string, first bool) (string, error) {
|
||||
if first {
|
||||
history, err := rwt.getSortedLogHistory()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(history) > 0 {
|
||||
return history[len(history)-1], nil
|
||||
}
|
||||
}
|
||||
|
||||
switch rwt.nameMode {
|
||||
case rollingNameModePostfix:
|
||||
return originalFileName + rollingLogHistoryDelimiter + time.Now().Format(rwt.timePattern), nil
|
||||
case rollingNameModePrefix:
|
||||
return time.Now().Format(rwt.timePattern) + rollingLogHistoryDelimiter + originalFileName, nil
|
||||
}
|
||||
return "", fmt.Errorf("Unknown rolling writer mode. Either postfix or prefix must be used")
|
||||
}
|
||||
|
||||
func (rwt *rollingFileWriterTime) String() string {
|
||||
return fmt.Sprintf("Rolling file writer (By TIME): filename: %s, archive: %s, archivefile: %s, maxInterval: %v, pattern: %s, maxRolls: %v",
|
||||
rwt.fileName,
|
||||
rollingArchiveTypesStringRepresentation[rwt.archiveType],
|
||||
rwt.archivePath,
|
||||
rwt.interval,
|
||||
rwt.timePattern,
|
||||
rwt.maxRolls)
|
||||
}
|
|
@ -0,0 +1,214 @@
|
|||
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||
// list of conditions and the following disclaimer.
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package seelog
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/smtp"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// Default subject phrase for sending emails.
|
||||
DefaultSubjectPhrase = "Diagnostic message from server: "
|
||||
|
||||
// Message subject pattern composed according to RFC 5321.
|
||||
rfc5321SubjectPattern = "From: %s <%s>\nSubject: %s\n\n"
|
||||
)
|
||||
|
||||
// smtpWriter is used to send emails via given SMTP-server.
|
||||
type smtpWriter struct {
|
||||
auth smtp.Auth
|
||||
hostName string
|
||||
hostPort string
|
||||
hostNameWithPort string
|
||||
senderAddress string
|
||||
senderName string
|
||||
recipientAddresses []string
|
||||
caCertDirPaths []string
|
||||
mailHeaders []string
|
||||
subject string
|
||||
}
|
||||
|
||||
// NewSMTPWriter returns a new SMTP-writer.
|
||||
func NewSMTPWriter(sa, sn string, ras []string, hn, hp, un, pwd string, cacdps []string, subj string, headers []string) *smtpWriter {
|
||||
return &smtpWriter{
|
||||
auth: smtp.PlainAuth("", un, pwd, hn),
|
||||
hostName: hn,
|
||||
hostPort: hp,
|
||||
hostNameWithPort: fmt.Sprintf("%s:%s", hn, hp),
|
||||
senderAddress: sa,
|
||||
senderName: sn,
|
||||
recipientAddresses: ras,
|
||||
caCertDirPaths: cacdps,
|
||||
subject: subj,
|
||||
mailHeaders: headers,
|
||||
}
|
||||
}
|
||||
|
||||
func prepareMessage(senderAddr, senderName, subject string, body []byte, headers []string) []byte {
|
||||
headerLines := fmt.Sprintf(rfc5321SubjectPattern, senderName, senderAddr, subject)
|
||||
// Build header lines if configured.
|
||||
if headers != nil && len(headers) > 0 {
|
||||
headerLines += strings.Join(headers, "\n")
|
||||
headerLines += "\n"
|
||||
}
|
||||
return append([]byte(headerLines), body...)
|
||||
}
|
||||
|
||||
// getTLSConfig gets paths of PEM files with certificates,
|
||||
// host server name and tries to create an appropriate TLS.Config.
|
||||
func getTLSConfig(pemFileDirPaths []string, hostName string) (config *tls.Config, err error) {
|
||||
if pemFileDirPaths == nil || len(pemFileDirPaths) == 0 {
|
||||
err = errors.New("invalid PEM file paths")
|
||||
return
|
||||
}
|
||||
pemEncodedContent := []byte{}
|
||||
var (
|
||||
e error
|
||||
bytes []byte
|
||||
)
|
||||
// Create a file-filter-by-extension, set aside non-pem files.
|
||||
pemFilePathFilter := func(fp string) bool {
|
||||
if filepath.Ext(fp) == ".pem" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
for _, pemFileDirPath := range pemFileDirPaths {
|
||||
pemFilePaths, err := getDirFilePaths(pemFileDirPath, pemFilePathFilter, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Put together all the PEM files to decode them as a whole byte slice.
|
||||
for _, pfp := range pemFilePaths {
|
||||
if bytes, e = ioutil.ReadFile(pfp); e == nil {
|
||||
pemEncodedContent = append(pemEncodedContent, bytes...)
|
||||
} else {
|
||||
return nil, fmt.Errorf("cannot read file: %s: %s", pfp, e.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
config = &tls.Config{RootCAs: x509.NewCertPool(), ServerName: hostName}
|
||||
isAppended := config.RootCAs.AppendCertsFromPEM(pemEncodedContent)
|
||||
if !isAppended {
|
||||
// Extract this into a separate error.
|
||||
err = errors.New("invalid PEM content")
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// SendMail accepts TLS configuration, connects to the server at addr,
|
||||
// switches to TLS if possible, authenticates with mechanism a if possible,
|
||||
// and then sends an email from address from, to addresses to, with message msg.
|
||||
func sendMailWithTLSConfig(config *tls.Config, addr string, a smtp.Auth, from string, to []string, msg []byte) error {
|
||||
c, err := smtp.Dial(addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Check if the server supports STARTTLS extension.
|
||||
if ok, _ := c.Extension("STARTTLS"); ok {
|
||||
if err = c.StartTLS(config); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Check if the server supports AUTH extension and use given smtp.Auth.
|
||||
if a != nil {
|
||||
if isSupported, _ := c.Extension("AUTH"); isSupported {
|
||||
if err = c.Auth(a); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
// Portion of code from the official smtp.SendMail function,
|
||||
// see http://golang.org/src/pkg/net/smtp/smtp.go.
|
||||
if err = c.Mail(from); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, addr := range to {
|
||||
if err = c.Rcpt(addr); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
w, err := c.Data()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = w.Write(msg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = w.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.Quit()
|
||||
}
|
||||
|
||||
// Write pushes a text message properly composed according to RFC 5321
|
||||
// to a post server, which sends it to the recipients.
|
||||
func (smtpw *smtpWriter) Write(data []byte) (int, error) {
|
||||
var err error
|
||||
|
||||
if smtpw.caCertDirPaths == nil {
|
||||
err = smtp.SendMail(
|
||||
smtpw.hostNameWithPort,
|
||||
smtpw.auth,
|
||||
smtpw.senderAddress,
|
||||
smtpw.recipientAddresses,
|
||||
prepareMessage(smtpw.senderAddress, smtpw.senderName, smtpw.subject, data, smtpw.mailHeaders),
|
||||
)
|
||||
} else {
|
||||
config, e := getTLSConfig(smtpw.caCertDirPaths, smtpw.hostName)
|
||||
if e != nil {
|
||||
return 0, e
|
||||
}
|
||||
err = sendMailWithTLSConfig(
|
||||
config,
|
||||
smtpw.hostNameWithPort,
|
||||
smtpw.auth,
|
||||
smtpw.senderAddress,
|
||||
smtpw.recipientAddresses,
|
||||
prepareMessage(smtpw.senderAddress, smtpw.senderName, smtpw.subject, data, smtpw.mailHeaders),
|
||||
)
|
||||
}
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return len(data), nil
|
||||
}
|
||||
|
||||
// Close closes down SMTP-connection.
|
||||
func (smtpw *smtpWriter) Close() error {
|
||||
// Do nothing as Write method opens and closes connection automatically.
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
sudo: false
|
||||
language: go
|
||||
go:
|
||||
- 1.3.x
|
||||
- 1.5.x
|
||||
- 1.6.x
|
||||
- 1.7.x
|
||||
- 1.8.x
|
||||
- 1.9.x
|
||||
- master
|
||||
matrix:
|
||||
allow_failures:
|
||||
- go: master
|
||||
fast_finish: true
|
||||
install:
|
||||
- # Do nothing. This is needed to prevent default install action "go get -t -v ./..." from happening here (we want it to happen inside script step).
|
||||
script:
|
||||
- go get -t -v ./...
|
||||
- diff -u <(echo -n) <(gofmt -d -s .)
|
||||
- go tool vet .
|
||||
- go test -v -race ./...
|
|
@ -0,0 +1,21 @@
|
|||
Copyright (c) 2005-2008 Dustin Sallings <dustin@spy.net>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
<http://www.opensource.org/licenses/mit-license.php>
|
|
@ -0,0 +1,124 @@
|
|||
# Humane Units [![Build Status](https://travis-ci.org/dustin/go-humanize.svg?branch=master)](https://travis-ci.org/dustin/go-humanize) [![GoDoc](https://godoc.org/github.com/dustin/go-humanize?status.svg)](https://godoc.org/github.com/dustin/go-humanize)
|
||||
|
||||
Just a few functions for helping humanize times and sizes.
|
||||
|
||||
`go get` it as `github.com/dustin/go-humanize`, import it as
|
||||
`"github.com/dustin/go-humanize"`, use it as `humanize`.
|
||||
|
||||
See [godoc](https://godoc.org/github.com/dustin/go-humanize) for
|
||||
complete documentation.
|
||||
|
||||
## Sizes
|
||||
|
||||
This lets you take numbers like `82854982` and convert them to useful
|
||||
strings like, `83 MB` or `79 MiB` (whichever you prefer).
|
||||
|
||||
Example:
|
||||
|
||||
```go
|
||||
fmt.Printf("That file is %s.", humanize.Bytes(82854982)) // That file is 83 MB.
|
||||
```
|
||||
|
||||
## Times
|
||||
|
||||
This lets you take a `time.Time` and spit it out in relative terms.
|
||||
For example, `12 seconds ago` or `3 days from now`.
|
||||
|
||||
Example:
|
||||
|
||||
```go
|
||||
fmt.Printf("This was touched %s.", humanize.Time(someTimeInstance)) // This was touched 7 hours ago.
|
||||
```
|
||||
|
||||
Thanks to Kyle Lemons for the time implementation from an IRC
|
||||
conversation one day. It's pretty neat.
|
||||
|
||||
## Ordinals
|
||||
|
||||
From a [mailing list discussion][odisc] where a user wanted to be able
|
||||
to label ordinals.
|
||||
|
||||
0 -> 0th
|
||||
1 -> 1st
|
||||
2 -> 2nd
|
||||
3 -> 3rd
|
||||
4 -> 4th
|
||||
[...]
|
||||
|
||||
Example:
|
||||
|
||||
```go
|
||||
fmt.Printf("You're my %s best friend.", humanize.Ordinal(193)) // You are my 193rd best friend.
|
||||
```
|
||||
|
||||
## Commas
|
||||
|
||||
Want to shove commas into numbers? Be my guest.
|
||||
|
||||
0 -> 0
|
||||
100 -> 100
|
||||
1000 -> 1,000
|
||||
1000000000 -> 1,000,000,000
|
||||
-100000 -> -100,000
|
||||
|
||||
Example:
|
||||
|
||||
```go
|
||||
fmt.Printf("You owe $%s.\n", humanize.Comma(6582491)) // You owe $6,582,491.
|
||||
```
|
||||
|
||||
## Ftoa
|
||||
|
||||
Nicer float64 formatter that removes trailing zeros.
|
||||
|
||||
```go
|
||||
fmt.Printf("%f", 2.24) // 2.240000
|
||||
fmt.Printf("%s", humanize.Ftoa(2.24)) // 2.24
|
||||
fmt.Printf("%f", 2.0) // 2.000000
|
||||
fmt.Printf("%s", humanize.Ftoa(2.0)) // 2
|
||||
```
|
||||
|
||||
## SI notation
|
||||
|
||||
Format numbers with [SI notation][sinotation].
|
||||
|
||||
Example:
|
||||
|
||||
```go
|
||||
humanize.SI(0.00000000223, "M") // 2.23 nM
|
||||
```
|
||||
|
||||
## English-specific functions
|
||||
|
||||
The following functions are in the `humanize/english` subpackage.
|
||||
|
||||
### Plurals
|
||||
|
||||
Simple English pluralization
|
||||
|
||||
```go
|
||||
english.PluralWord(1, "object", "") // object
|
||||
english.PluralWord(42, "object", "") // objects
|
||||
english.PluralWord(2, "bus", "") // buses
|
||||
english.PluralWord(99, "locus", "loci") // loci
|
||||
|
||||
english.Plural(1, "object", "") // 1 object
|
||||
english.Plural(42, "object", "") // 42 objects
|
||||
english.Plural(2, "bus", "") // 2 buses
|
||||
english.Plural(99, "locus", "loci") // 99 loci
|
||||
```
|
||||
|
||||
### Word series
|
||||
|
||||
Format comma-separated words lists with conjuctions:
|
||||
|
||||
```go
|
||||
english.WordSeries([]string{"foo"}, "and") // foo
|
||||
english.WordSeries([]string{"foo", "bar"}, "and") // foo and bar
|
||||
english.WordSeries([]string{"foo", "bar", "baz"}, "and") // foo, bar and baz
|
||||
|
||||
english.OxfordWordSeries([]string{"foo", "bar", "baz"}, "and") // foo, bar, and baz
|
||||
```
|
||||
|
||||
[odisc]: https://groups.google.com/d/topic/golang-nuts/l8NhI74jl-4/discussion
|
||||
[sinotation]: http://en.wikipedia.org/wiki/Metric_prefix
|
|
@ -0,0 +1,31 @@
|
|||
package humanize
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
)
|
||||
|
||||
// order of magnitude (to a max order)
|
||||
func oomm(n, b *big.Int, maxmag int) (float64, int) {
|
||||
mag := 0
|
||||
m := &big.Int{}
|
||||
for n.Cmp(b) >= 0 {
|
||||
n.DivMod(n, b, m)
|
||||
mag++
|
||||
if mag == maxmag && maxmag >= 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return float64(n.Int64()) + (float64(m.Int64()) / float64(b.Int64())), mag
|
||||
}
|
||||
|
||||
// total order of magnitude
|
||||
// (same as above, but with no upper limit)
|
||||
func oom(n, b *big.Int) (float64, int) {
|
||||
mag := 0
|
||||
m := &big.Int{}
|
||||
for n.Cmp(b) >= 0 {
|
||||
n.DivMod(n, b, m)
|
||||
mag++
|
||||
}
|
||||
return float64(n.Int64()) + (float64(m.Int64()) / float64(b.Int64())), mag
|
||||
}
|
|
@ -0,0 +1,173 @@
|
|||
package humanize
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
var (
|
||||
bigIECExp = big.NewInt(1024)
|
||||
|
||||
// BigByte is one byte in bit.Ints
|
||||
BigByte = big.NewInt(1)
|
||||
// BigKiByte is 1,024 bytes in bit.Ints
|
||||
BigKiByte = (&big.Int{}).Mul(BigByte, bigIECExp)
|
||||
// BigMiByte is 1,024 k bytes in bit.Ints
|
||||
BigMiByte = (&big.Int{}).Mul(BigKiByte, bigIECExp)
|
||||
// BigGiByte is 1,024 m bytes in bit.Ints
|
||||
BigGiByte = (&big.Int{}).Mul(BigMiByte, bigIECExp)
|
||||
// BigTiByte is 1,024 g bytes in bit.Ints
|
||||
BigTiByte = (&big.Int{}).Mul(BigGiByte, bigIECExp)
|
||||
// BigPiByte is 1,024 t bytes in bit.Ints
|
||||
BigPiByte = (&big.Int{}).Mul(BigTiByte, bigIECExp)
|
||||
// BigEiByte is 1,024 p bytes in bit.Ints
|
||||
BigEiByte = (&big.Int{}).Mul(BigPiByte, bigIECExp)
|
||||
// BigZiByte is 1,024 e bytes in bit.Ints
|
||||
BigZiByte = (&big.Int{}).Mul(BigEiByte, bigIECExp)
|
||||
// BigYiByte is 1,024 z bytes in bit.Ints
|
||||
BigYiByte = (&big.Int{}).Mul(BigZiByte, bigIECExp)
|
||||
)
|
||||
|
||||
var (
|
||||
bigSIExp = big.NewInt(1000)
|
||||
|
||||
// BigSIByte is one SI byte in big.Ints
|
||||
BigSIByte = big.NewInt(1)
|
||||
// BigKByte is 1,000 SI bytes in big.Ints
|
||||
BigKByte = (&big.Int{}).Mul(BigSIByte, bigSIExp)
|
||||
// BigMByte is 1,000 SI k bytes in big.Ints
|
||||
BigMByte = (&big.Int{}).Mul(BigKByte, bigSIExp)
|
||||
// BigGByte is 1,000 SI m bytes in big.Ints
|
||||
BigGByte = (&big.Int{}).Mul(BigMByte, bigSIExp)
|
||||
// BigTByte is 1,000 SI g bytes in big.Ints
|
||||
BigTByte = (&big.Int{}).Mul(BigGByte, bigSIExp)
|
||||
// BigPByte is 1,000 SI t bytes in big.Ints
|
||||
BigPByte = (&big.Int{}).Mul(BigTByte, bigSIExp)
|
||||
// BigEByte is 1,000 SI p bytes in big.Ints
|
||||
BigEByte = (&big.Int{}).Mul(BigPByte, bigSIExp)
|
||||
// BigZByte is 1,000 SI e bytes in big.Ints
|
||||
BigZByte = (&big.Int{}).Mul(BigEByte, bigSIExp)
|
||||
// BigYByte is 1,000 SI z bytes in big.Ints
|
||||
BigYByte = (&big.Int{}).Mul(BigZByte, bigSIExp)
|
||||
)
|
||||
|
||||
var bigBytesSizeTable = map[string]*big.Int{
|
||||
"b": BigByte,
|
||||
"kib": BigKiByte,
|
||||
"kb": BigKByte,
|
||||
"mib": BigMiByte,
|
||||
"mb": BigMByte,
|
||||
"gib": BigGiByte,
|
||||
"gb": BigGByte,
|
||||
"tib": BigTiByte,
|
||||
"tb": BigTByte,
|
||||
"pib": BigPiByte,
|
||||
"pb": BigPByte,
|
||||
"eib": BigEiByte,
|
||||
"eb": BigEByte,
|
||||
"zib": BigZiByte,
|
||||
"zb": BigZByte,
|
||||
"yib": BigYiByte,
|
||||
"yb": BigYByte,
|
||||
// Without suffix
|
||||
"": BigByte,
|
||||
"ki": BigKiByte,
|
||||
"k": BigKByte,
|
||||
"mi": BigMiByte,
|
||||
"m": BigMByte,
|
||||
"gi": BigGiByte,
|
||||
"g": BigGByte,
|
||||
"ti": BigTiByte,
|
||||
"t": BigTByte,
|
||||
"pi": BigPiByte,
|
||||
"p": BigPByte,
|
||||
"ei": BigEiByte,
|
||||
"e": BigEByte,
|
||||
"z": BigZByte,
|
||||
"zi": BigZiByte,
|
||||
"y": BigYByte,
|
||||
"yi": BigYiByte,
|
||||
}
|
||||
|
||||
var ten = big.NewInt(10)
|
||||
|
||||
func humanateBigBytes(s, base *big.Int, sizes []string) string {
|
||||
if s.Cmp(ten) < 0 {
|
||||
return fmt.Sprintf("%d B", s)
|
||||
}
|
||||
c := (&big.Int{}).Set(s)
|
||||
val, mag := oomm(c, base, len(sizes)-1)
|
||||
suffix := sizes[mag]
|
||||
f := "%.0f %s"
|
||||
if val < 10 {
|
||||
f = "%.1f %s"
|
||||
}
|
||||
|
||||
return fmt.Sprintf(f, val, suffix)
|
||||
|
||||
}
|
||||
|
||||
// BigBytes produces a human readable representation of an SI size.
|
||||
//
|
||||
// See also: ParseBigBytes.
|
||||
//
|
||||
// BigBytes(82854982) -> 83 MB
|
||||
func BigBytes(s *big.Int) string {
|
||||
sizes := []string{"B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"}
|
||||
return humanateBigBytes(s, bigSIExp, sizes)
|
||||
}
|
||||
|
||||
// BigIBytes produces a human readable representation of an IEC size.
|
||||
//
|
||||
// See also: ParseBigBytes.
|
||||
//
|
||||
// BigIBytes(82854982) -> 79 MiB
|
||||
func BigIBytes(s *big.Int) string {
|
||||
sizes := []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"}
|
||||
return humanateBigBytes(s, bigIECExp, sizes)
|
||||
}
|
||||
|
||||
// ParseBigBytes parses a string representation of bytes into the number
|
||||
// of bytes it represents.
|
||||
//
|
||||
// See also: BigBytes, BigIBytes.
|
||||
//
|
||||
// ParseBigBytes("42 MB") -> 42000000, nil
|
||||
// ParseBigBytes("42 mib") -> 44040192, nil
|
||||
func ParseBigBytes(s string) (*big.Int, error) {
|
||||
lastDigit := 0
|
||||
hasComma := false
|
||||
for _, r := range s {
|
||||
if !(unicode.IsDigit(r) || r == '.' || r == ',') {
|
||||
break
|
||||
}
|
||||
if r == ',' {
|
||||
hasComma = true
|
||||
}
|
||||
lastDigit++
|
||||
}
|
||||
|
||||
num := s[:lastDigit]
|
||||
if hasComma {
|
||||
num = strings.Replace(num, ",", "", -1)
|
||||
}
|
||||
|
||||
val := &big.Rat{}
|
||||
_, err := fmt.Sscanf(num, "%f", val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
extra := strings.ToLower(strings.TrimSpace(s[lastDigit:]))
|
||||
if m, ok := bigBytesSizeTable[extra]; ok {
|
||||
mv := (&big.Rat{}).SetInt(m)
|
||||
val.Mul(val, mv)
|
||||
rv := &big.Int{}
|
||||
rv.Div(val.Num(), val.Denom())
|
||||
return rv, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("unhandled size name: %v", extra)
|
||||
}
|
|
@ -0,0 +1,143 @@
|
|||
package humanize
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// IEC Sizes.
|
||||
// kibis of bits
|
||||
const (
|
||||
Byte = 1 << (iota * 10)
|
||||
KiByte
|
||||
MiByte
|
||||
GiByte
|
||||
TiByte
|
||||
PiByte
|
||||
EiByte
|
||||
)
|
||||
|
||||
// SI Sizes.
|
||||
const (
|
||||
IByte = 1
|
||||
KByte = IByte * 1000
|
||||
MByte = KByte * 1000
|
||||
GByte = MByte * 1000
|
||||
TByte = GByte * 1000
|
||||
PByte = TByte * 1000
|
||||
EByte = PByte * 1000
|
||||
)
|
||||
|
||||
var bytesSizeTable = map[string]uint64{
|
||||
"b": Byte,
|
||||
"kib": KiByte,
|
||||
"kb": KByte,
|
||||
"mib": MiByte,
|
||||
"mb": MByte,
|
||||
"gib": GiByte,
|
||||
"gb": GByte,
|
||||
"tib": TiByte,
|
||||
"tb": TByte,
|
||||
"pib": PiByte,
|
||||
"pb": PByte,
|
||||
"eib": EiByte,
|
||||
"eb": EByte,
|
||||
// Without suffix
|
||||
"": Byte,
|
||||
"ki": KiByte,
|
||||
"k": KByte,
|
||||
"mi": MiByte,
|
||||
"m": MByte,
|
||||
"gi": GiByte,
|
||||
"g": GByte,
|
||||
"ti": TiByte,
|
||||
"t": TByte,
|
||||
"pi": PiByte,
|
||||
"p": PByte,
|
||||
"ei": EiByte,
|
||||
"e": EByte,
|
||||
}
|
||||
|
||||
func logn(n, b float64) float64 {
|
||||
return math.Log(n) / math.Log(b)
|
||||
}
|
||||
|
||||
func humanateBytes(s uint64, base float64, sizes []string) string {
|
||||
if s < 10 {
|
||||
return fmt.Sprintf("%d B", s)
|
||||
}
|
||||
e := math.Floor(logn(float64(s), base))
|
||||
suffix := sizes[int(e)]
|
||||
val := math.Floor(float64(s)/math.Pow(base, e)*10+0.5) / 10
|
||||
f := "%.0f %s"
|
||||
if val < 10 {
|
||||
f = "%.1f %s"
|
||||
}
|
||||
|
||||
return fmt.Sprintf(f, val, suffix)
|
||||
}
|
||||
|
||||
// Bytes produces a human readable representation of an SI size.
|
||||
//
|
||||
// See also: ParseBytes.
|
||||
//
|
||||
// Bytes(82854982) -> 83 MB
|
||||
func Bytes(s uint64) string {
|
||||
sizes := []string{"B", "kB", "MB", "GB", "TB", "PB", "EB"}
|
||||
return humanateBytes(s, 1000, sizes)
|
||||
}
|
||||
|
||||
// IBytes produces a human readable representation of an IEC size.
|
||||
//
|
||||
// See also: ParseBytes.
|
||||
//
|
||||
// IBytes(82854982) -> 79 MiB
|
||||
func IBytes(s uint64) string {
|
||||
sizes := []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"}
|
||||
return humanateBytes(s, 1024, sizes)
|
||||
}
|
||||
|
||||
// ParseBytes parses a string representation of bytes into the number
|
||||
// of bytes it represents.
|
||||
//
|
||||
// See Also: Bytes, IBytes.
|
||||
//
|
||||
// ParseBytes("42 MB") -> 42000000, nil
|
||||
// ParseBytes("42 mib") -> 44040192, nil
|
||||
func ParseBytes(s string) (uint64, error) {
|
||||
lastDigit := 0
|
||||
hasComma := false
|
||||
for _, r := range s {
|
||||
if !(unicode.IsDigit(r) || r == '.' || r == ',') {
|
||||
break
|
||||
}
|
||||
if r == ',' {
|
||||
hasComma = true
|
||||
}
|
||||
lastDigit++
|
||||
}
|
||||
|
||||
num := s[:lastDigit]
|
||||
if hasComma {
|
||||
num = strings.Replace(num, ",", "", -1)
|
||||
}
|
||||
|
||||
f, err := strconv.ParseFloat(num, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
extra := strings.ToLower(strings.TrimSpace(s[lastDigit:]))
|
||||
if m, ok := bytesSizeTable[extra]; ok {
|
||||
f *= float64(m)
|
||||
if f >= math.MaxUint64 {
|
||||
return 0, fmt.Errorf("too large: %v", s)
|
||||
}
|
||||
return uint64(f), nil
|
||||
}
|
||||
|
||||
return 0, fmt.Errorf("unhandled size name: %v", extra)
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
package humanize
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"math"
|
||||
"math/big"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Comma produces a string form of the given number in base 10 with
|
||||
// commas after every three orders of magnitude.
|
||||
//
|
||||
// e.g. Comma(834142) -> 834,142
|
||||
func Comma(v int64) string {
|
||||
sign := ""
|
||||
|
||||
// Min int64 can't be negated to a usable value, so it has to be special cased.
|
||||
if v == math.MinInt64 {
|
||||
return "-9,223,372,036,854,775,808"
|
||||
}
|
||||
|
||||
if v < 0 {
|
||||
sign = "-"
|
||||
v = 0 - v
|
||||
}
|
||||
|
||||
parts := []string{"", "", "", "", "", "", ""}
|
||||
j := len(parts) - 1
|
||||
|
||||
for v > 999 {
|
||||
parts[j] = strconv.FormatInt(v%1000, 10)
|
||||
switch len(parts[j]) {
|
||||
case 2:
|
||||
parts[j] = "0" + parts[j]
|
||||
case 1:
|
||||
parts[j] = "00" + parts[j]
|
||||
}
|
||||
v = v / 1000
|
||||
j--
|
||||
}
|
||||
parts[j] = strconv.Itoa(int(v))
|
||||
return sign + strings.Join(parts[j:], ",")
|
||||
}
|
||||
|
||||
// Commaf produces a string form of the given number in base 10 with
|
||||
// commas after every three orders of magnitude.
|
||||
//
|
||||
// e.g. Commaf(834142.32) -> 834,142.32
|
||||
func Commaf(v float64) string {
|
||||
buf := &bytes.Buffer{}
|
||||
if v < 0 {
|
||||
buf.Write([]byte{'-'})
|
||||
v = 0 - v
|
||||
}
|
||||
|
||||
comma := []byte{','}
|
||||
|
||||
parts := strings.Split(strconv.FormatFloat(v, 'f', -1, 64), ".")
|
||||
pos := 0
|
||||
if len(parts[0])%3 != 0 {
|
||||
pos += len(parts[0]) % 3
|
||||
buf.WriteString(parts[0][:pos])
|
||||
buf.Write(comma)
|
||||
}
|
||||
for ; pos < len(parts[0]); pos += 3 {
|
||||
buf.WriteString(parts[0][pos : pos+3])
|
||||
buf.Write(comma)
|
||||
}
|
||||
buf.Truncate(buf.Len() - 1)
|
||||
|
||||
if len(parts) > 1 {
|
||||
buf.Write([]byte{'.'})
|
||||
buf.WriteString(parts[1])
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// CommafWithDigits works like the Commaf but limits the resulting
|
||||
// string to the given number of decimal places.
|
||||
//
|
||||
// e.g. CommafWithDigits(834142.32, 1) -> 834,142.3
|
||||
func CommafWithDigits(f float64, decimals int) string {
|
||||
return stripTrailingDigits(Commaf(f), decimals)
|
||||
}
|
||||
|
||||
// BigComma produces a string form of the given big.Int in base 10
|
||||
// with commas after every three orders of magnitude.
|
||||
func BigComma(b *big.Int) string {
|
||||
sign := ""
|
||||
if b.Sign() < 0 {
|
||||
sign = "-"
|
||||
b.Abs(b)
|
||||
}
|
||||
|
||||
athousand := big.NewInt(1000)
|
||||
c := (&big.Int{}).Set(b)
|
||||
_, m := oom(c, athousand)
|
||||
parts := make([]string, m+1)
|
||||
j := len(parts) - 1
|
||||
|
||||
mod := &big.Int{}
|
||||
for b.Cmp(athousand) >= 0 {
|
||||
b.DivMod(b, athousand, mod)
|
||||
parts[j] = strconv.FormatInt(mod.Int64(), 10)
|
||||
switch len(parts[j]) {
|
||||
case 2:
|
||||
parts[j] = "0" + parts[j]
|
||||
case 1:
|
||||
parts[j] = "00" + parts[j]
|
||||
}
|
||||
j--
|
||||
}
|
||||
parts[j] = strconv.Itoa(int(b.Int64()))
|
||||
return sign + strings.Join(parts[j:], ",")
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
// +build go1.6
|
||||
|
||||
package humanize
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"math/big"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// BigCommaf produces a string form of the given big.Float in base 10
|
||||
// with commas after every three orders of magnitude.
|
||||
func BigCommaf(v *big.Float) string {
|
||||
buf := &bytes.Buffer{}
|
||||
if v.Sign() < 0 {
|
||||
buf.Write([]byte{'-'})
|
||||
v.Abs(v)
|
||||
}
|
||||
|
||||
comma := []byte{','}
|
||||
|
||||
parts := strings.Split(v.Text('f', -1), ".")
|
||||
pos := 0
|
||||
if len(parts[0])%3 != 0 {
|
||||
pos += len(parts[0]) % 3
|
||||
buf.WriteString(parts[0][:pos])
|
||||
buf.Write(comma)
|
||||
}
|
||||
for ; pos < len(parts[0]); pos += 3 {
|
||||
buf.WriteString(parts[0][pos : pos+3])
|
||||
buf.Write(comma)
|
||||
}
|
||||
buf.Truncate(buf.Len() - 1)
|
||||
|
||||
if len(parts) > 1 {
|
||||
buf.Write([]byte{'.'})
|
||||
buf.WriteString(parts[1])
|
||||
}
|
||||
return buf.String()
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
package humanize
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func stripTrailingZeros(s string) string {
|
||||
offset := len(s) - 1
|
||||
for offset > 0 {
|
||||
if s[offset] == '.' {
|
||||
offset--
|
||||
break
|
||||
}
|
||||
if s[offset] != '0' {
|
||||
break
|
||||
}
|
||||
offset--
|
||||
}
|
||||
return s[:offset+1]
|
||||
}
|
||||
|
||||
func stripTrailingDigits(s string, digits int) string {
|
||||
if i := strings.Index(s, "."); i >= 0 {
|
||||
if digits <= 0 {
|
||||
return s[:i]
|
||||
}
|
||||
i++
|
||||
if i+digits >= len(s) {
|
||||
return s
|
||||
}
|
||||
return s[:i+digits]
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Ftoa converts a float to a string with no trailing zeros.
|
||||
func Ftoa(num float64) string {
|
||||
return stripTrailingZeros(strconv.FormatFloat(num, 'f', 6, 64))
|
||||
}
|
||||
|
||||
// FtoaWithDigits converts a float to a string but limits the resulting string
|
||||
// to the given number of decimal places, and no trailing zeros.
|
||||
func FtoaWithDigits(num float64, digits int) string {
|
||||
return stripTrailingZeros(stripTrailingDigits(strconv.FormatFloat(num, 'f', 6, 64), digits))
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
Package humanize converts boring ugly numbers to human-friendly strings and back.
|
||||
|
||||
Durations can be turned into strings such as "3 days ago", numbers
|
||||
representing sizes like 82854982 into useful strings like, "83 MB" or
|
||||
"79 MiB" (whichever you prefer).
|
||||
*/
|
||||
package humanize
|
|
@ -0,0 +1,192 @@
|
|||
package humanize
|
||||
|
||||
/*
|
||||
Slightly adapted from the source to fit go-humanize.
|
||||
|
||||
Author: https://github.com/gorhill
|
||||
Source: https://gist.github.com/gorhill/5285193
|
||||
|
||||
*/
|
||||
|
||||
import (
|
||||
"math"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var (
|
||||
renderFloatPrecisionMultipliers = [...]float64{
|
||||
1,
|
||||
10,
|
||||
100,
|
||||
1000,
|
||||
10000,
|
||||
100000,
|
||||
1000000,
|
||||
10000000,
|
||||
100000000,
|
||||
1000000000,
|
||||
}
|
||||
|
||||
renderFloatPrecisionRounders = [...]float64{
|
||||
0.5,
|
||||
0.05,
|
||||
0.005,
|
||||
0.0005,
|
||||
0.00005,
|
||||
0.000005,
|
||||
0.0000005,
|
||||
0.00000005,
|
||||
0.000000005,
|
||||
0.0000000005,
|
||||
}
|
||||
)
|
||||
|
||||
// FormatFloat produces a formatted number as string based on the following user-specified criteria:
|
||||
// * thousands separator
|
||||
// * decimal separator
|
||||
// * decimal precision
|
||||
//
|
||||
// Usage: s := RenderFloat(format, n)
|
||||
// The format parameter tells how to render the number n.
|
||||
//
|
||||
// See examples: http://play.golang.org/p/LXc1Ddm1lJ
|
||||
//
|
||||
// Examples of format strings, given n = 12345.6789:
|
||||
// "#,###.##" => "12,345.67"
|
||||
// "#,###." => "12,345"
|
||||
// "#,###" => "12345,678"
|
||||
// "#\u202F###,##" => "12 345,68"
|
||||
// "#.###,###### => 12.345,678900
|
||||
// "" (aka default format) => 12,345.67
|
||||
//
|
||||
// The highest precision allowed is 9 digits after the decimal symbol.
|
||||
// There is also a version for integer number, FormatInteger(),
|
||||
// which is convenient for calls within template.
|
||||
func FormatFloat(format string, n float64) string {
|
||||
// Special cases:
|
||||
// NaN = "NaN"
|
||||
// +Inf = "+Infinity"
|
||||
// -Inf = "-Infinity"
|
||||
if math.IsNaN(n) {
|
||||
return "NaN"
|
||||
}
|
||||
if n > math.MaxFloat64 {
|
||||
return "Infinity"
|
||||
}
|
||||
if n < -math.MaxFloat64 {
|
||||
return "-Infinity"
|
||||
}
|
||||
|
||||
// default format
|
||||
precision := 2
|
||||
decimalStr := "."
|
||||
thousandStr := ","
|
||||
positiveStr := ""
|
||||
negativeStr := "-"
|
||||
|
||||
if len(format) > 0 {
|
||||
format := []rune(format)
|
||||
|
||||
// If there is an explicit format directive,
|
||||
// then default values are these:
|
||||
precision = 9
|
||||
thousandStr = ""
|
||||
|
||||
// collect indices of meaningful formatting directives
|
||||
formatIndx := []int{}
|
||||
for i, char := range format {
|
||||
if char != '#' && char != '0' {
|
||||
formatIndx = append(formatIndx, i)
|
||||
}
|
||||
}
|
||||
|
||||
if len(formatIndx) > 0 {
|
||||
// Directive at index 0:
|
||||
// Must be a '+'
|
||||
// Raise an error if not the case
|
||||
// index: 0123456789
|
||||
// +0.000,000
|
||||
// +000,000.0
|
||||
// +0000.00
|
||||
// +0000
|
||||
if formatIndx[0] == 0 {
|
||||
if format[formatIndx[0]] != '+' {
|
||||
panic("RenderFloat(): invalid positive sign directive")
|
||||
}
|
||||
positiveStr = "+"
|
||||
formatIndx = formatIndx[1:]
|
||||
}
|
||||
|
||||
// Two directives:
|
||||
// First is thousands separator
|
||||
// Raise an error if not followed by 3-digit
|
||||
// 0123456789
|
||||
// 0.000,000
|
||||
// 000,000.00
|
||||
if len(formatIndx) == 2 {
|
||||
if (formatIndx[1] - formatIndx[0]) != 4 {
|
||||
panic("RenderFloat(): thousands separator directive must be followed by 3 digit-specifiers")
|
||||
}
|
||||
thousandStr = string(format[formatIndx[0]])
|
||||
formatIndx = formatIndx[1:]
|
||||
}
|
||||
|
||||
// One directive:
|
||||
// Directive is decimal separator
|
||||
// The number of digit-specifier following the separator indicates wanted precision
|
||||
// 0123456789
|
||||
// 0.00
|
||||
// 000,0000
|
||||
if len(formatIndx) == 1 {
|
||||
decimalStr = string(format[formatIndx[0]])
|
||||
precision = len(format) - formatIndx[0] - 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// generate sign part
|
||||
var signStr string
|
||||
if n >= 0.000000001 {
|
||||
signStr = positiveStr
|
||||
} else if n <= -0.000000001 {
|
||||
signStr = negativeStr
|
||||
n = -n
|
||||
} else {
|
||||
signStr = ""
|
||||
n = 0.0
|
||||
}
|
||||
|
||||
// split number into integer and fractional parts
|
||||
intf, fracf := math.Modf(n + renderFloatPrecisionRounders[precision])
|
||||
|
||||
// generate integer part string
|
||||
intStr := strconv.FormatInt(int64(intf), 10)
|
||||
|
||||
// add thousand separator if required
|
||||
if len(thousandStr) > 0 {
|
||||
for i := len(intStr); i > 3; {
|
||||
i -= 3
|
||||
intStr = intStr[:i] + thousandStr + intStr[i:]
|
||||
}
|
||||
}
|
||||
|
||||
// no fractional part, we can leave now
|
||||
if precision == 0 {
|
||||
return signStr + intStr
|
||||
}
|
||||
|
||||
// generate fractional part
|
||||
fracStr := strconv.Itoa(int(fracf * renderFloatPrecisionMultipliers[precision]))
|
||||
// may need padding
|
||||
if len(fracStr) < precision {
|
||||
fracStr = "000000000000000"[:precision-len(fracStr)] + fracStr
|
||||
}
|
||||
|
||||
return signStr + intStr + decimalStr + fracStr
|
||||
}
|
||||
|
||||
// FormatInteger produces a formatted number as string.
|
||||
// See FormatFloat.
|
||||
func FormatInteger(format string, n int) string {
|
||||
return FormatFloat(format, float64(n))
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
package humanize
|
||||
|
||||
import "strconv"
|
||||
|
||||
// Ordinal gives you the input number in a rank/ordinal format.
|
||||
//
|
||||
// Ordinal(3) -> 3rd
|
||||
func Ordinal(x int) string {
|
||||
suffix := "th"
|
||||
switch x % 10 {
|
||||
case 1:
|
||||
if x%100 != 11 {
|
||||
suffix = "st"
|
||||
}
|
||||
case 2:
|
||||
if x%100 != 12 {
|
||||
suffix = "nd"
|
||||
}
|
||||
case 3:
|
||||
if x%100 != 13 {
|
||||
suffix = "rd"
|
||||
}
|
||||
}
|
||||
return strconv.Itoa(x) + suffix
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
package humanize
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math"
|
||||
"regexp"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var siPrefixTable = map[float64]string{
|
||||
-24: "y", // yocto
|
||||
-21: "z", // zepto
|
||||
-18: "a", // atto
|
||||
-15: "f", // femto
|
||||
-12: "p", // pico
|
||||
-9: "n", // nano
|
||||
-6: "µ", // micro
|
||||
-3: "m", // milli
|
||||
0: "",
|
||||
3: "k", // kilo
|
||||
6: "M", // mega
|
||||
9: "G", // giga
|
||||
12: "T", // tera
|
||||
15: "P", // peta
|
||||
18: "E", // exa
|
||||
21: "Z", // zetta
|
||||
24: "Y", // yotta
|
||||
}
|
||||
|
||||
var revSIPrefixTable = revfmap(siPrefixTable)
|
||||
|
||||
// revfmap reverses the map and precomputes the power multiplier
|
||||
func revfmap(in map[float64]string) map[string]float64 {
|
||||
rv := map[string]float64{}
|
||||
for k, v := range in {
|
||||
rv[v] = math.Pow(10, k)
|
||||
}
|
||||
return rv
|
||||
}
|
||||
|
||||
var riParseRegex *regexp.Regexp
|
||||
|
||||
func init() {
|
||||
ri := `^([\-0-9.]+)\s?([`
|
||||
for _, v := range siPrefixTable {
|
||||
ri += v
|
||||
}
|
||||
ri += `]?)(.*)`
|
||||
|
||||
riParseRegex = regexp.MustCompile(ri)
|
||||
}
|
||||
|
||||
// ComputeSI finds the most appropriate SI prefix for the given number
|
||||
// and returns the prefix along with the value adjusted to be within
|
||||
// that prefix.
|
||||
//
|
||||
// See also: SI, ParseSI.
|
||||
//
|
||||
// e.g. ComputeSI(2.2345e-12) -> (2.2345, "p")
|
||||
func ComputeSI(input float64) (float64, string) {
|
||||
if input == 0 {
|
||||
return 0, ""
|
||||
}
|
||||
mag := math.Abs(input)
|
||||
exponent := math.Floor(logn(mag, 10))
|
||||
exponent = math.Floor(exponent/3) * 3
|
||||
|
||||
value := mag / math.Pow(10, exponent)
|
||||
|
||||
// Handle special case where value is exactly 1000.0
|
||||
// Should return 1 M instead of 1000 k
|
||||
if value == 1000.0 {
|
||||
exponent += 3
|
||||
value = mag / math.Pow(10, exponent)
|
||||
}
|
||||
|
||||
value = math.Copysign(value, input)
|
||||
|
||||
prefix := siPrefixTable[exponent]
|
||||
return value, prefix
|
||||
}
|
||||
|
||||
// SI returns a string with default formatting.
|
||||
//
|
||||
// SI uses Ftoa to format float value, removing trailing zeros.
|
||||
//
|
||||
// See also: ComputeSI, ParseSI.
|
||||
//
|
||||
// e.g. SI(1000000, "B") -> 1 MB
|
||||
// e.g. SI(2.2345e-12, "F") -> 2.2345 pF
|
||||
func SI(input float64, unit string) string {
|
||||
value, prefix := ComputeSI(input)
|
||||
return Ftoa(value) + " " + prefix + unit
|
||||
}
|
||||
|
||||
// SIWithDigits works like SI but limits the resulting string to the
|
||||
// given number of decimal places.
|
||||
//
|
||||
// e.g. SIWithDigits(1000000, 0, "B") -> 1 MB
|
||||
// e.g. SIWithDigits(2.2345e-12, 2, "F") -> 2.23 pF
|
||||
func SIWithDigits(input float64, decimals int, unit string) string {
|
||||
value, prefix := ComputeSI(input)
|
||||
return FtoaWithDigits(value, decimals) + " " + prefix + unit
|
||||
}
|
||||
|
||||
var errInvalid = errors.New("invalid input")
|
||||
|
||||
// ParseSI parses an SI string back into the number and unit.
|
||||
//
|
||||
// See also: SI, ComputeSI.
|
||||
//
|
||||
// e.g. ParseSI("2.2345 pF") -> (2.2345e-12, "F", nil)
|
||||
func ParseSI(input string) (float64, string, error) {
|
||||
found := riParseRegex.FindStringSubmatch(input)
|
||||
if len(found) != 4 {
|
||||
return 0, "", errInvalid
|
||||
}
|
||||
mag := revSIPrefixTable[found[2]]
|
||||
unit := found[3]
|
||||
|
||||
base, err := strconv.ParseFloat(found[1], 64)
|
||||
return base * mag, unit, err
|
||||
}
|
|
@ -0,0 +1,117 @@
|
|||
package humanize
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"sort"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Seconds-based time units
|
||||
const (
|
||||
Day = 24 * time.Hour
|
||||
Week = 7 * Day
|
||||
Month = 30 * Day
|
||||
Year = 12 * Month
|
||||
LongTime = 37 * Year
|
||||
)
|
||||
|
||||
// Time formats a time into a relative string.
|
||||
//
|
||||
// Time(someT) -> "3 weeks ago"
|
||||
func Time(then time.Time) string {
|
||||
return RelTime(then, time.Now(), "ago", "from now")
|
||||
}
|
||||
|
||||
// A RelTimeMagnitude struct contains a relative time point at which
|
||||
// the relative format of time will switch to a new format string. A
|
||||
// slice of these in ascending order by their "D" field is passed to
|
||||
// CustomRelTime to format durations.
|
||||
//
|
||||
// The Format field is a string that may contain a "%s" which will be
|
||||
// replaced with the appropriate signed label (e.g. "ago" or "from
|
||||
// now") and a "%d" that will be replaced by the quantity.
|
||||
//
|
||||
// The DivBy field is the amount of time the time difference must be
|
||||
// divided by in order to display correctly.
|
||||
//
|
||||
// e.g. if D is 2*time.Minute and you want to display "%d minutes %s"
|
||||
// DivBy should be time.Minute so whatever the duration is will be
|
||||
// expressed in minutes.
|
||||
type RelTimeMagnitude struct {
|
||||
D time.Duration
|
||||
Format string
|
||||
DivBy time.Duration
|
||||
}
|
||||
|
||||
var defaultMagnitudes = []RelTimeMagnitude{
|
||||
{time.Second, "now", time.Second},
|
||||
{2 * time.Second, "1 second %s", 1},
|
||||
{time.Minute, "%d seconds %s", time.Second},
|
||||
{2 * time.Minute, "1 minute %s", 1},
|
||||
{time.Hour, "%d minutes %s", time.Minute},
|
||||
{2 * time.Hour, "1 hour %s", 1},
|
||||
{Day, "%d hours %s", time.Hour},
|
||||
{2 * Day, "1 day %s", 1},
|
||||
{Week, "%d days %s", Day},
|
||||
{2 * Week, "1 week %s", 1},
|
||||
{Month, "%d weeks %s", Week},
|
||||
{2 * Month, "1 month %s", 1},
|
||||
{Year, "%d months %s", Month},
|
||||
{18 * Month, "1 year %s", 1},
|
||||
{2 * Year, "2 years %s", 1},
|
||||
{LongTime, "%d years %s", Year},
|
||||
{math.MaxInt64, "a long while %s", 1},
|
||||
}
|
||||
|
||||
// RelTime formats a time into a relative string.
|
||||
//
|
||||
// It takes two times and two labels. In addition to the generic time
|
||||
// delta string (e.g. 5 minutes), the labels are used applied so that
|
||||
// the label corresponding to the smaller time is applied.
|
||||
//
|
||||
// RelTime(timeInPast, timeInFuture, "earlier", "later") -> "3 weeks earlier"
|
||||
func RelTime(a, b time.Time, albl, blbl string) string {
|
||||
return CustomRelTime(a, b, albl, blbl, defaultMagnitudes)
|
||||
}
|
||||
|
||||
// CustomRelTime formats a time into a relative string.
|
||||
//
|
||||
// It takes two times two labels and a table of relative time formats.
|
||||
// In addition to the generic time delta string (e.g. 5 minutes), the
|
||||
// labels are used applied so that the label corresponding to the
|
||||
// smaller time is applied.
|
||||
func CustomRelTime(a, b time.Time, albl, blbl string, magnitudes []RelTimeMagnitude) string {
|
||||
lbl := albl
|
||||
diff := b.Sub(a)
|
||||
|
||||
if a.After(b) {
|
||||
lbl = blbl
|
||||
diff = a.Sub(b)
|
||||
}
|
||||
|
||||
n := sort.Search(len(magnitudes), func(i int) bool {
|
||||
return magnitudes[i].D > diff
|
||||
})
|
||||
|
||||
if n >= len(magnitudes) {
|
||||
n = len(magnitudes) - 1
|
||||
}
|
||||
mag := magnitudes[n]
|
||||
args := []interface{}{}
|
||||
escaped := false
|
||||
for _, ch := range mag.Format {
|
||||
if escaped {
|
||||
switch ch {
|
||||
case 's':
|
||||
args = append(args, lbl)
|
||||
case 'd':
|
||||
args = append(args, diff/mag.DivBy)
|
||||
}
|
||||
escaped = false
|
||||
} else {
|
||||
escaped = ch == '%'
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf(mag.Format, args...)
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
|
@ -0,0 +1,11 @@
|
|||
language: go
|
||||
go:
|
||||
- 1.7.x
|
||||
- tip
|
||||
sudo: false
|
||||
before_install:
|
||||
- go get github.com/axw/gocov/gocov
|
||||
- go get github.com/mattn/goveralls
|
||||
- if ! go get github.com/golang/tools/cmd/cover; then go get golang.org/x/tools/cmd/cover; fi
|
||||
script:
|
||||
- $HOME/gopath/bin/goveralls -service=travis-ci
|
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Fatih Arslan
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,163 @@
|
|||
# Structs [![GoDoc](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)](http://godoc.org/github.com/fatih/structs) [![Build Status](http://img.shields.io/travis/fatih/structs.svg?style=flat-square)](https://travis-ci.org/fatih/structs) [![Coverage Status](http://img.shields.io/coveralls/fatih/structs.svg?style=flat-square)](https://coveralls.io/r/fatih/structs)
|
||||
|
||||
Structs contains various utilities to work with Go (Golang) structs. It was
|
||||
initially used by me to convert a struct into a `map[string]interface{}`. With
|
||||
time I've added other utilities for structs. It's basically a high level
|
||||
package based on primitives from the reflect package. Feel free to add new
|
||||
functions or improve the existing code.
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
go get github.com/fatih/structs
|
||||
```
|
||||
|
||||
## Usage and Examples
|
||||
|
||||
Just like the standard lib `strings`, `bytes` and co packages, `structs` has
|
||||
many global functions to manipulate or organize your struct data. Lets define
|
||||
and declare a struct:
|
||||
|
||||
```go
|
||||
type Server struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
ID int
|
||||
Enabled bool
|
||||
users []string // not exported
|
||||
http.Server // embedded
|
||||
}
|
||||
|
||||
server := &Server{
|
||||
Name: "gopher",
|
||||
ID: 123456,
|
||||
Enabled: true,
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
// Convert a struct to a map[string]interface{}
|
||||
// => {"Name":"gopher", "ID":123456, "Enabled":true}
|
||||
m := structs.Map(server)
|
||||
|
||||
// Convert the values of a struct to a []interface{}
|
||||
// => ["gopher", 123456, true]
|
||||
v := structs.Values(server)
|
||||
|
||||
// Convert the names of a struct to a []string
|
||||
// (see "Names methods" for more info about fields)
|
||||
n := structs.Names(server)
|
||||
|
||||
// Convert the values of a struct to a []*Field
|
||||
// (see "Field methods" for more info about fields)
|
||||
f := structs.Fields(server)
|
||||
|
||||
// Return the struct name => "Server"
|
||||
n := structs.Name(server)
|
||||
|
||||
// Check if any field of a struct is initialized or not.
|
||||
h := structs.HasZero(server)
|
||||
|
||||
// Check if all fields of a struct is initialized or not.
|
||||
z := structs.IsZero(server)
|
||||
|
||||
// Check if server is a struct or a pointer to struct
|
||||
i := structs.IsStruct(server)
|
||||
```
|
||||
|
||||
### Struct methods
|
||||
|
||||
The structs functions can be also used as independent methods by creating a new
|
||||
`*structs.Struct`. This is handy if you want to have more control over the
|
||||
structs (such as retrieving a single Field).
|
||||
|
||||
```go
|
||||
// Create a new struct type:
|
||||
s := structs.New(server)
|
||||
|
||||
m := s.Map() // Get a map[string]interface{}
|
||||
v := s.Values() // Get a []interface{}
|
||||
f := s.Fields() // Get a []*Field
|
||||
n := s.Names() // Get a []string
|
||||
f := s.Field(name) // Get a *Field based on the given field name
|
||||
f, ok := s.FieldOk(name) // Get a *Field based on the given field name
|
||||
n := s.Name() // Get the struct name
|
||||
h := s.HasZero() // Check if any field is initialized
|
||||
z := s.IsZero() // Check if all fields are initialized
|
||||
```
|
||||
|
||||
### Field methods
|
||||
|
||||
We can easily examine a single Field for more detail. Below you can see how we
|
||||
get and interact with various field methods:
|
||||
|
||||
|
||||
```go
|
||||
s := structs.New(server)
|
||||
|
||||
// Get the Field struct for the "Name" field
|
||||
name := s.Field("Name")
|
||||
|
||||
// Get the underlying value, value => "gopher"
|
||||
value := name.Value().(string)
|
||||
|
||||
// Set the field's value
|
||||
name.Set("another gopher")
|
||||
|
||||
// Get the field's kind, kind => "string"
|
||||
name.Kind()
|
||||
|
||||
// Check if the field is exported or not
|
||||
if name.IsExported() {
|
||||
fmt.Println("Name field is exported")
|
||||
}
|
||||
|
||||
// Check if the value is a zero value, such as "" for string, 0 for int
|
||||
if !name.IsZero() {
|
||||
fmt.Println("Name is initialized")
|
||||
}
|
||||
|
||||
// Check if the field is an anonymous (embedded) field
|
||||
if !name.IsEmbedded() {
|
||||
fmt.Println("Name is not an embedded field")
|
||||
}
|
||||
|
||||
// Get the Field's tag value for tag name "json", tag value => "name,omitempty"
|
||||
tagValue := name.Tag("json")
|
||||
```
|
||||
|
||||
Nested structs are supported too:
|
||||
|
||||
```go
|
||||
addrField := s.Field("Server").Field("Addr")
|
||||
|
||||
// Get the value for addr
|
||||
a := addrField.Value().(string)
|
||||
|
||||
// Or get all fields
|
||||
httpServer := s.Field("Server").Fields()
|
||||
```
|
||||
|
||||
We can also get a slice of Fields from the Struct type to iterate over all
|
||||
fields. This is handy if you wish to examine all fields:
|
||||
|
||||
```go
|
||||
s := structs.New(server)
|
||||
|
||||
for _, f := range s.Fields() {
|
||||
fmt.Printf("field name: %+v\n", f.Name())
|
||||
|
||||
if f.IsExported() {
|
||||
fmt.Printf("value : %+v\n", f.Value())
|
||||
fmt.Printf("is zero : %+v\n", f.IsZero())
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Credits
|
||||
|
||||
* [Fatih Arslan](https://github.com/fatih)
|
||||
* [Cihangir Savas](https://github.com/cihangir)
|
||||
|
||||
## License
|
||||
|
||||
The MIT License (MIT) - see LICENSE.md for more details
|
|
@ -0,0 +1,141 @@
|
|||
package structs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
var (
|
||||
errNotExported = errors.New("field is not exported")
|
||||
errNotSettable = errors.New("field is not settable")
|
||||
)
|
||||
|
||||
// Field represents a single struct field that encapsulates high level
|
||||
// functions around the field.
|
||||
type Field struct {
|
||||
value reflect.Value
|
||||
field reflect.StructField
|
||||
defaultTag string
|
||||
}
|
||||
|
||||
// Tag returns the value associated with key in the tag string. If there is no
|
||||
// such key in the tag, Tag returns the empty string.
|
||||
func (f *Field) Tag(key string) string {
|
||||
return f.field.Tag.Get(key)
|
||||
}
|
||||
|
||||
// Value returns the underlying value of the field. It panics if the field
|
||||
// is not exported.
|
||||
func (f *Field) Value() interface{} {
|
||||
return f.value.Interface()
|
||||
}
|
||||
|
||||
// IsEmbedded returns true if the given field is an anonymous field (embedded)
|
||||
func (f *Field) IsEmbedded() bool {
|
||||
return f.field.Anonymous
|
||||
}
|
||||
|
||||
// IsExported returns true if the given field is exported.
|
||||
func (f *Field) IsExported() bool {
|
||||
return f.field.PkgPath == ""
|
||||
}
|
||||
|
||||
// IsZero returns true if the given field is not initialized (has a zero value).
|
||||
// It panics if the field is not exported.
|
||||
func (f *Field) IsZero() bool {
|
||||
zero := reflect.Zero(f.value.Type()).Interface()
|
||||
current := f.Value()
|
||||
|
||||
return reflect.DeepEqual(current, zero)
|
||||
}
|
||||
|
||||
// Name returns the name of the given field
|
||||
func (f *Field) Name() string {
|
||||
return f.field.Name
|
||||
}
|
||||
|
||||
// Kind returns the fields kind, such as "string", "map", "bool", etc ..
|
||||
func (f *Field) Kind() reflect.Kind {
|
||||
return f.value.Kind()
|
||||
}
|
||||
|
||||
// Set sets the field to given value v. It returns an error if the field is not
|
||||
// settable (not addressable or not exported) or if the given value's type
|
||||
// doesn't match the fields type.
|
||||
func (f *Field) Set(val interface{}) error {
|
||||
// we can't set unexported fields, so be sure this field is exported
|
||||
if !f.IsExported() {
|
||||
return errNotExported
|
||||
}
|
||||
|
||||
// do we get here? not sure...
|
||||
if !f.value.CanSet() {
|
||||
return errNotSettable
|
||||
}
|
||||
|
||||
given := reflect.ValueOf(val)
|
||||
|
||||
if f.value.Kind() != given.Kind() {
|
||||
return fmt.Errorf("wrong kind. got: %s want: %s", given.Kind(), f.value.Kind())
|
||||
}
|
||||
|
||||
f.value.Set(given)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Zero sets the field to its zero value. It returns an error if the field is not
|
||||
// settable (not addressable or not exported).
|
||||
func (f *Field) Zero() error {
|
||||
zero := reflect.Zero(f.value.Type()).Interface()
|
||||
return f.Set(zero)
|
||||
}
|
||||
|
||||
// Fields returns a slice of Fields. This is particular handy to get the fields
|
||||
// of a nested struct . A struct tag with the content of "-" ignores the
|
||||
// checking of that particular field. Example:
|
||||
//
|
||||
// // Field is ignored by this package.
|
||||
// Field *http.Request `structs:"-"`
|
||||
//
|
||||
// It panics if field is not exported or if field's kind is not struct
|
||||
func (f *Field) Fields() []*Field {
|
||||
return getFields(f.value, f.defaultTag)
|
||||
}
|
||||
|
||||
// Field returns the field from a nested struct. It panics if the nested struct
|
||||
// is not exported or if the field was not found.
|
||||
func (f *Field) Field(name string) *Field {
|
||||
field, ok := f.FieldOk(name)
|
||||
if !ok {
|
||||
panic("field not found")
|
||||
}
|
||||
|
||||
return field
|
||||
}
|
||||
|
||||
// FieldOk returns the field from a nested struct. The boolean returns whether
|
||||
// the field was found (true) or not (false).
|
||||
func (f *Field) FieldOk(name string) (*Field, bool) {
|
||||
value := &f.value
|
||||
// value must be settable so we need to make sure it holds the address of the
|
||||
// variable and not a copy, so we can pass the pointer to strctVal instead of a
|
||||
// copy (which is not assigned to any variable, hence not settable).
|
||||
// see "https://blog.golang.org/laws-of-reflection#TOC_8."
|
||||
if f.value.Kind() != reflect.Ptr {
|
||||
a := f.value.Addr()
|
||||
value = &a
|
||||
}
|
||||
v := strctVal(value.Interface())
|
||||
t := v.Type()
|
||||
|
||||
field, ok := t.FieldByName(name)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return &Field{
|
||||
field: field,
|
||||
value: v.FieldByName(name),
|
||||
}, true
|
||||
}
|
|
@ -0,0 +1,586 @@
|
|||
// Package structs contains various utilities functions to work with structs.
|
||||
package structs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"reflect"
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultTagName is the default tag name for struct fields which provides
|
||||
// a more granular to tweak certain structs. Lookup the necessary functions
|
||||
// for more info.
|
||||
DefaultTagName = "structs" // struct's field default tag name
|
||||
)
|
||||
|
||||
// Struct encapsulates a struct type to provide several high level functions
|
||||
// around the struct.
|
||||
type Struct struct {
|
||||
raw interface{}
|
||||
value reflect.Value
|
||||
TagName string
|
||||
}
|
||||
|
||||
// New returns a new *Struct with the struct s. It panics if the s's kind is
|
||||
// not struct.
|
||||
func New(s interface{}) *Struct {
|
||||
return &Struct{
|
||||
raw: s,
|
||||
value: strctVal(s),
|
||||
TagName: DefaultTagName,
|
||||
}
|
||||
}
|
||||
|
||||
// Map converts the given struct to a map[string]interface{}, where the keys
|
||||
// of the map are the field names and the values of the map the associated
|
||||
// values of the fields. The default key string is the struct field name but
|
||||
// can be changed in the struct field's tag value. The "structs" key in the
|
||||
// struct's field tag value is the key name. Example:
|
||||
//
|
||||
// // Field appears in map as key "myName".
|
||||
// Name string `structs:"myName"`
|
||||
//
|
||||
// A tag value with the content of "-" ignores that particular field. Example:
|
||||
//
|
||||
// // Field is ignored by this package.
|
||||
// Field bool `structs:"-"`
|
||||
//
|
||||
// A tag value with the content of "string" uses the stringer to get the value. Example:
|
||||
//
|
||||
// // The value will be output of Animal's String() func.
|
||||
// // Map will panic if Animal does not implement String().
|
||||
// Field *Animal `structs:"field,string"`
|
||||
//
|
||||
// A tag value with the option of "flatten" used in a struct field is to flatten its fields
|
||||
// in the output map. Example:
|
||||
//
|
||||
// // The FieldStruct's fields will be flattened into the output map.
|
||||
// FieldStruct time.Time `structs:",flatten"`
|
||||
//
|
||||
// A tag value with the option of "omitnested" stops iterating further if the type
|
||||
// is a struct. Example:
|
||||
//
|
||||
// // Field is not processed further by this package.
|
||||
// Field time.Time `structs:"myName,omitnested"`
|
||||
// Field *http.Request `structs:",omitnested"`
|
||||
//
|
||||
// A tag value with the option of "omitempty" ignores that particular field if
|
||||
// the field value is empty. Example:
|
||||
//
|
||||
// // Field appears in map as key "myName", but the field is
|
||||
// // skipped if empty.
|
||||
// Field string `structs:"myName,omitempty"`
|
||||
//
|
||||
// // Field appears in map as key "Field" (the default), but
|
||||
// // the field is skipped if empty.
|
||||
// Field string `structs:",omitempty"`
|
||||
//
|
||||
// Note that only exported fields of a struct can be accessed, non exported
|
||||
// fields will be neglected.
|
||||
func (s *Struct) Map() map[string]interface{} {
|
||||
out := make(map[string]interface{})
|
||||
s.FillMap(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// FillMap is the same as Map. Instead of returning the output, it fills the
|
||||
// given map.
|
||||
func (s *Struct) FillMap(out map[string]interface{}) {
|
||||
if out == nil {
|
||||
return
|
||||
}
|
||||
|
||||
fields := s.structFields()
|
||||
|
||||
for _, field := range fields {
|
||||
name := field.Name
|
||||
val := s.value.FieldByName(name)
|
||||
isSubStruct := false
|
||||
var finalVal interface{}
|
||||
|
||||
tagName, tagOpts := parseTag(field.Tag.Get(s.TagName))
|
||||
if tagName != "" {
|
||||
name = tagName
|
||||
}
|
||||
|
||||
// if the value is a zero value and the field is marked as omitempty do
|
||||
// not include
|
||||
if tagOpts.Has("omitempty") {
|
||||
zero := reflect.Zero(val.Type()).Interface()
|
||||
current := val.Interface()
|
||||
|
||||
if reflect.DeepEqual(current, zero) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if !tagOpts.Has("omitnested") {
|
||||
finalVal = s.nested(val)
|
||||
|
||||
v := reflect.ValueOf(val.Interface())
|
||||
if v.Kind() == reflect.Ptr {
|
||||
v = v.Elem()
|
||||
}
|
||||
|
||||
switch v.Kind() {
|
||||
case reflect.Map, reflect.Struct:
|
||||
isSubStruct = true
|
||||
}
|
||||
} else {
|
||||
finalVal = val.Interface()
|
||||
}
|
||||
|
||||
if tagOpts.Has("string") {
|
||||
s, ok := val.Interface().(fmt.Stringer)
|
||||
if ok {
|
||||
out[name] = s.String()
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if isSubStruct && (tagOpts.Has("flatten")) {
|
||||
for k := range finalVal.(map[string]interface{}) {
|
||||
out[k] = finalVal.(map[string]interface{})[k]
|
||||
}
|
||||
} else {
|
||||
out[name] = finalVal
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Values converts the given s struct's field values to a []interface{}. A
|
||||
// struct tag with the content of "-" ignores the that particular field.
|
||||
// Example:
|
||||
//
|
||||
// // Field is ignored by this package.
|
||||
// Field int `structs:"-"`
|
||||
//
|
||||
// A value with the option of "omitnested" stops iterating further if the type
|
||||
// is a struct. Example:
|
||||
//
|
||||
// // Fields is not processed further by this package.
|
||||
// Field time.Time `structs:",omitnested"`
|
||||
// Field *http.Request `structs:",omitnested"`
|
||||
//
|
||||
// A tag value with the option of "omitempty" ignores that particular field and
|
||||
// is not added to the values if the field value is empty. Example:
|
||||
//
|
||||
// // Field is skipped if empty
|
||||
// Field string `structs:",omitempty"`
|
||||
//
|
||||
// Note that only exported fields of a struct can be accessed, non exported
|
||||
// fields will be neglected.
|
||||
func (s *Struct) Values() []interface{} {
|
||||
fields := s.structFields()
|
||||
|
||||
var t []interface{}
|
||||
|
||||
for _, field := range fields {
|
||||
val := s.value.FieldByName(field.Name)
|
||||
|
||||
_, tagOpts := parseTag(field.Tag.Get(s.TagName))
|
||||
|
||||
// if the value is a zero value and the field is marked as omitempty do
|
||||
// not include
|
||||
if tagOpts.Has("omitempty") {
|
||||
zero := reflect.Zero(val.Type()).Interface()
|
||||
current := val.Interface()
|
||||
|
||||
if reflect.DeepEqual(current, zero) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if tagOpts.Has("string") {
|
||||
s, ok := val.Interface().(fmt.Stringer)
|
||||
if ok {
|
||||
t = append(t, s.String())
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if IsStruct(val.Interface()) && !tagOpts.Has("omitnested") {
|
||||
// look out for embedded structs, and convert them to a
|
||||
// []interface{} to be added to the final values slice
|
||||
for _, embeddedVal := range Values(val.Interface()) {
|
||||
t = append(t, embeddedVal)
|
||||
}
|
||||
} else {
|
||||
t = append(t, val.Interface())
|
||||
}
|
||||
}
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
// Fields returns a slice of Fields. A struct tag with the content of "-"
|
||||
// ignores the checking of that particular field. Example:
|
||||
//
|
||||
// // Field is ignored by this package.
|
||||
// Field bool `structs:"-"`
|
||||
//
|
||||
// It panics if s's kind is not struct.
|
||||
func (s *Struct) Fields() []*Field {
|
||||
return getFields(s.value, s.TagName)
|
||||
}
|
||||
|
||||
// Names returns a slice of field names. A struct tag with the content of "-"
|
||||
// ignores the checking of that particular field. Example:
|
||||
//
|
||||
// // Field is ignored by this package.
|
||||
// Field bool `structs:"-"`
|
||||
//
|
||||
// It panics if s's kind is not struct.
|
||||
func (s *Struct) Names() []string {
|
||||
fields := getFields(s.value, s.TagName)
|
||||
|
||||
names := make([]string, len(fields))
|
||||
|
||||
for i, field := range fields {
|
||||
names[i] = field.Name()
|
||||
}
|
||||
|
||||
return names
|
||||
}
|
||||
|
||||
func getFields(v reflect.Value, tagName string) []*Field {
|
||||
if v.Kind() == reflect.Ptr {
|
||||
v = v.Elem()
|
||||
}
|
||||
|
||||
t := v.Type()
|
||||
|
||||
var fields []*Field
|
||||
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
field := t.Field(i)
|
||||
|
||||
if tag := field.Tag.Get(tagName); tag == "-" {
|
||||
continue
|
||||
}
|
||||
|
||||
f := &Field{
|
||||
field: field,
|
||||
value: v.FieldByName(field.Name),
|
||||
}
|
||||
|
||||
fields = append(fields, f)
|
||||
|
||||
}
|
||||
|
||||
return fields
|
||||
}
|
||||
|
||||
// Field returns a new Field struct that provides several high level functions
|
||||
// around a single struct field entity. It panics if the field is not found.
|
||||
func (s *Struct) Field(name string) *Field {
|
||||
f, ok := s.FieldOk(name)
|
||||
if !ok {
|
||||
panic("field not found")
|
||||
}
|
||||
|
||||
return f
|
||||
}
|
||||
|
||||
// FieldOk returns a new Field struct that provides several high level functions
|
||||
// around a single struct field entity. The boolean returns true if the field
|
||||
// was found.
|
||||
func (s *Struct) FieldOk(name string) (*Field, bool) {
|
||||
t := s.value.Type()
|
||||
|
||||
field, ok := t.FieldByName(name)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return &Field{
|
||||
field: field,
|
||||
value: s.value.FieldByName(name),
|
||||
defaultTag: s.TagName,
|
||||
}, true
|
||||
}
|
||||
|
||||
// IsZero returns true if all fields in a struct is a zero value (not
|
||||
// initialized) A struct tag with the content of "-" ignores the checking of
|
||||
// that particular field. Example:
|
||||
//
|
||||
// // Field is ignored by this package.
|
||||
// Field bool `structs:"-"`
|
||||
//
|
||||
// A value with the option of "omitnested" stops iterating further if the type
|
||||
// is a struct. Example:
|
||||
//
|
||||
// // Field is not processed further by this package.
|
||||
// Field time.Time `structs:"myName,omitnested"`
|
||||
// Field *http.Request `structs:",omitnested"`
|
||||
//
|
||||
// Note that only exported fields of a struct can be accessed, non exported
|
||||
// fields will be neglected. It panics if s's kind is not struct.
|
||||
func (s *Struct) IsZero() bool {
|
||||
fields := s.structFields()
|
||||
|
||||
for _, field := range fields {
|
||||
val := s.value.FieldByName(field.Name)
|
||||
|
||||
_, tagOpts := parseTag(field.Tag.Get(s.TagName))
|
||||
|
||||
if IsStruct(val.Interface()) && !tagOpts.Has("omitnested") {
|
||||
ok := IsZero(val.Interface())
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// zero value of the given field, such as "" for string, 0 for int
|
||||
zero := reflect.Zero(val.Type()).Interface()
|
||||
|
||||
// current value of the given field
|
||||
current := val.Interface()
|
||||
|
||||
if !reflect.DeepEqual(current, zero) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// HasZero returns true if a field in a struct is not initialized (zero value).
|
||||
// A struct tag with the content of "-" ignores the checking of that particular
|
||||
// field. Example:
|
||||
//
|
||||
// // Field is ignored by this package.
|
||||
// Field bool `structs:"-"`
|
||||
//
|
||||
// A value with the option of "omitnested" stops iterating further if the type
|
||||
// is a struct. Example:
|
||||
//
|
||||
// // Field is not processed further by this package.
|
||||
// Field time.Time `structs:"myName,omitnested"`
|
||||
// Field *http.Request `structs:",omitnested"`
|
||||
//
|
||||
// Note that only exported fields of a struct can be accessed, non exported
|
||||
// fields will be neglected. It panics if s's kind is not struct.
|
||||
func (s *Struct) HasZero() bool {
|
||||
fields := s.structFields()
|
||||
|
||||
for _, field := range fields {
|
||||
val := s.value.FieldByName(field.Name)
|
||||
|
||||
_, tagOpts := parseTag(field.Tag.Get(s.TagName))
|
||||
|
||||
if IsStruct(val.Interface()) && !tagOpts.Has("omitnested") {
|
||||
ok := HasZero(val.Interface())
|
||||
if ok {
|
||||
return true
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// zero value of the given field, such as "" for string, 0 for int
|
||||
zero := reflect.Zero(val.Type()).Interface()
|
||||
|
||||
// current value of the given field
|
||||
current := val.Interface()
|
||||
|
||||
if reflect.DeepEqual(current, zero) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Name returns the structs's type name within its package. For more info refer
|
||||
// to Name() function.
|
||||
func (s *Struct) Name() string {
|
||||
return s.value.Type().Name()
|
||||
}
|
||||
|
||||
// structFields returns the exported struct fields for a given s struct. This
|
||||
// is a convenient helper method to avoid duplicate code in some of the
|
||||
// functions.
|
||||
func (s *Struct) structFields() []reflect.StructField {
|
||||
t := s.value.Type()
|
||||
|
||||
var f []reflect.StructField
|
||||
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
field := t.Field(i)
|
||||
// we can't access the value of unexported fields
|
||||
if field.PkgPath != "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// don't check if it's omitted
|
||||
if tag := field.Tag.Get(s.TagName); tag == "-" {
|
||||
continue
|
||||
}
|
||||
|
||||
f = append(f, field)
|
||||
}
|
||||
|
||||
return f
|
||||
}
|
||||
|
||||
func strctVal(s interface{}) reflect.Value {
|
||||
v := reflect.ValueOf(s)
|
||||
|
||||
// if pointer get the underlying element≤
|
||||
for v.Kind() == reflect.Ptr {
|
||||
v = v.Elem()
|
||||
}
|
||||
|
||||
if v.Kind() != reflect.Struct {
|
||||
panic("not struct")
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// Map converts the given struct to a map[string]interface{}. For more info
|
||||
// refer to Struct types Map() method. It panics if s's kind is not struct.
|
||||
func Map(s interface{}) map[string]interface{} {
|
||||
return New(s).Map()
|
||||
}
|
||||
|
||||
// FillMap is the same as Map. Instead of returning the output, it fills the
|
||||
// given map.
|
||||
func FillMap(s interface{}, out map[string]interface{}) {
|
||||
New(s).FillMap(out)
|
||||
}
|
||||
|
||||
// Values converts the given struct to a []interface{}. For more info refer to
|
||||
// Struct types Values() method. It panics if s's kind is not struct.
|
||||
func Values(s interface{}) []interface{} {
|
||||
return New(s).Values()
|
||||
}
|
||||
|
||||
// Fields returns a slice of *Field. For more info refer to Struct types
|
||||
// Fields() method. It panics if s's kind is not struct.
|
||||
func Fields(s interface{}) []*Field {
|
||||
return New(s).Fields()
|
||||
}
|
||||
|
||||
// Names returns a slice of field names. For more info refer to Struct types
|
||||
// Names() method. It panics if s's kind is not struct.
|
||||
func Names(s interface{}) []string {
|
||||
return New(s).Names()
|
||||
}
|
||||
|
||||
// IsZero returns true if all fields is equal to a zero value. For more info
|
||||
// refer to Struct types IsZero() method. It panics if s's kind is not struct.
|
||||
func IsZero(s interface{}) bool {
|
||||
return New(s).IsZero()
|
||||
}
|
||||
|
||||
// HasZero returns true if any field is equal to a zero value. For more info
|
||||
// refer to Struct types HasZero() method. It panics if s's kind is not struct.
|
||||
func HasZero(s interface{}) bool {
|
||||
return New(s).HasZero()
|
||||
}
|
||||
|
||||
// IsStruct returns true if the given variable is a struct or a pointer to
|
||||
// struct.
|
||||
func IsStruct(s interface{}) bool {
|
||||
v := reflect.ValueOf(s)
|
||||
if v.Kind() == reflect.Ptr {
|
||||
v = v.Elem()
|
||||
}
|
||||
|
||||
// uninitialized zero value of a struct
|
||||
if v.Kind() == reflect.Invalid {
|
||||
return false
|
||||
}
|
||||
|
||||
return v.Kind() == reflect.Struct
|
||||
}
|
||||
|
||||
// Name returns the structs's type name within its package. It returns an
|
||||
// empty string for unnamed types. It panics if s's kind is not struct.
|
||||
func Name(s interface{}) string {
|
||||
return New(s).Name()
|
||||
}
|
||||
|
||||
// nested retrieves recursively all types for the given value and returns the
|
||||
// nested value.
|
||||
func (s *Struct) nested(val reflect.Value) interface{} {
|
||||
var finalVal interface{}
|
||||
|
||||
v := reflect.ValueOf(val.Interface())
|
||||
if v.Kind() == reflect.Ptr {
|
||||
v = v.Elem()
|
||||
}
|
||||
|
||||
switch v.Kind() {
|
||||
case reflect.Struct:
|
||||
n := New(val.Interface())
|
||||
n.TagName = s.TagName
|
||||
m := n.Map()
|
||||
|
||||
// do not add the converted value if there are no exported fields, ie:
|
||||
// time.Time
|
||||
if len(m) == 0 {
|
||||
finalVal = val.Interface()
|
||||
} else {
|
||||
finalVal = m
|
||||
}
|
||||
case reflect.Map:
|
||||
// get the element type of the map
|
||||
mapElem := val.Type()
|
||||
switch val.Type().Kind() {
|
||||
case reflect.Ptr, reflect.Array, reflect.Map,
|
||||
reflect.Slice, reflect.Chan:
|
||||
mapElem = val.Type().Elem()
|
||||
if mapElem.Kind() == reflect.Ptr {
|
||||
mapElem = mapElem.Elem()
|
||||
}
|
||||
}
|
||||
|
||||
// only iterate over struct types, ie: map[string]StructType,
|
||||
// map[string][]StructType,
|
||||
if mapElem.Kind() == reflect.Struct ||
|
||||
(mapElem.Kind() == reflect.Slice &&
|
||||
mapElem.Elem().Kind() == reflect.Struct) {
|
||||
m := make(map[string]interface{}, val.Len())
|
||||
for _, k := range val.MapKeys() {
|
||||
m[k.String()] = s.nested(val.MapIndex(k))
|
||||
}
|
||||
finalVal = m
|
||||
break
|
||||
}
|
||||
|
||||
// TODO(arslan): should this be optional?
|
||||
finalVal = val.Interface()
|
||||
case reflect.Slice, reflect.Array:
|
||||
if val.Type().Kind() == reflect.Interface {
|
||||
finalVal = val.Interface()
|
||||
break
|
||||
}
|
||||
|
||||
// TODO(arslan): should this be optional?
|
||||
// do not iterate of non struct types, just pass the value. Ie: []int,
|
||||
// []string, co... We only iterate further if it's a struct.
|
||||
// i.e []foo or []*foo
|
||||
if val.Type().Elem().Kind() != reflect.Struct &&
|
||||
!(val.Type().Elem().Kind() == reflect.Ptr &&
|
||||
val.Type().Elem().Elem().Kind() == reflect.Struct) {
|
||||
finalVal = val.Interface()
|
||||
break
|
||||
}
|
||||
|
||||
slices := make([]interface{}, val.Len(), val.Len())
|
||||
for x := 0; x < val.Len(); x++ {
|
||||
slices[x] = s.nested(val.Index(x))
|
||||
}
|
||||
finalVal = slices
|
||||
default:
|
||||
finalVal = val.Interface()
|
||||
}
|
||||
|
||||
return finalVal
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package structs
|
||||
|
||||
import "strings"
|
||||
|
||||
// tagOptions contains a slice of tag options
|
||||
type tagOptions []string
|
||||
|
||||
// Has returns true if the given optiton is available in tagOptions
|
||||
func (t tagOptions) Has(opt string) bool {
|
||||
for _, tagOpt := range t {
|
||||
if tagOpt == opt {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// parseTag splits a struct field's tag into its name and a list of options
|
||||
// which comes after a name. A tag is in the form of: "name,option1,option2".
|
||||
// The name can be neglectected.
|
||||
func parseTag(tag string) (string, tagOptions) {
|
||||
// tag is one of followings:
|
||||
// ""
|
||||
// "name"
|
||||
// "name,opt"
|
||||
// "name,opt,opt2"
|
||||
// ",opt"
|
||||
|
||||
res := strings.Split(tag, ",")
|
||||
return res[0], res[1:]
|
||||
}
|
|
@ -0,0 +1,354 @@
|
|||
Mozilla Public License, version 2.0
|
||||
|
||||
1. Definitions
|
||||
|
||||
1.1. “Contributor”
|
||||
|
||||
means each individual or legal entity that creates, contributes to the
|
||||
creation of, or owns Covered Software.
|
||||
|
||||
1.2. “Contributor Version”
|
||||
|
||||
means the combination of the Contributions of others (if any) used by a
|
||||
Contributor and that particular Contributor’s Contribution.
|
||||
|
||||
1.3. “Contribution”
|
||||
|
||||
means Covered Software of a particular Contributor.
|
||||
|
||||
1.4. “Covered Software”
|
||||
|
||||
means Source Code Form to which the initial Contributor has attached the
|
||||
notice in Exhibit A, the Executable Form of such Source Code Form, and
|
||||
Modifications of such Source Code Form, in each case including portions
|
||||
thereof.
|
||||
|
||||
1.5. “Incompatible With Secondary Licenses”
|
||||
means
|
||||
|
||||
a. that the initial Contributor has attached the notice described in
|
||||
Exhibit B to the Covered Software; or
|
||||
|
||||
b. that the Covered Software was made available under the terms of version
|
||||
1.1 or earlier of the License, but not also under the terms of a
|
||||
Secondary License.
|
||||
|
||||
1.6. “Executable Form”
|
||||
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
1.7. “Larger Work”
|
||||
|
||||
means a work that combines Covered Software with other material, in a separate
|
||||
file or files, that is not Covered Software.
|
||||
|
||||
1.8. “License”
|
||||
|
||||
means this document.
|
||||
|
||||
1.9. “Licensable”
|
||||
|
||||
means having the right to grant, to the maximum extent possible, whether at the
|
||||
time of the initial grant or subsequently, any and all of the rights conveyed by
|
||||
this License.
|
||||
|
||||
1.10. “Modifications”
|
||||
|
||||
means any of the following:
|
||||
|
||||
a. any file in Source Code Form that results from an addition to, deletion
|
||||
from, or modification of the contents of Covered Software; or
|
||||
|
||||
b. any new file in Source Code Form that contains any Covered Software.
|
||||
|
||||
1.11. “Patent Claims” of a Contributor
|
||||
|
||||
means any patent claim(s), including without limitation, method, process,
|
||||
and apparatus claims, in any patent Licensable by such Contributor that
|
||||
would be infringed, but for the grant of the License, by the making,
|
||||
using, selling, offering for sale, having made, import, or transfer of
|
||||
either its Contributions or its Contributor Version.
|
||||
|
||||
1.12. “Secondary License”
|
||||
|
||||
means either the GNU General Public License, Version 2.0, the GNU Lesser
|
||||
General Public License, Version 2.1, the GNU Affero General Public
|
||||
License, Version 3.0, or any later versions of those licenses.
|
||||
|
||||
1.13. “Source Code Form”
|
||||
|
||||
means the form of the work preferred for making modifications.
|
||||
|
||||
1.14. “You” (or “Your”)
|
||||
|
||||
means an individual or a legal entity exercising rights under this
|
||||
License. For legal entities, “You” includes any entity that controls, is
|
||||
controlled by, or is under common control with You. For purposes of this
|
||||
definition, “control” means (a) the power, direct or indirect, to cause
|
||||
the direction or management of such entity, whether by contract or
|
||||
otherwise, or (b) ownership of more than fifty percent (50%) of the
|
||||
outstanding shares or beneficial ownership of such entity.
|
||||
|
||||
|
||||
2. License Grants and Conditions
|
||||
|
||||
2.1. Grants
|
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
a. under intellectual property rights (other than patent or trademark)
|
||||
Licensable by such Contributor to use, reproduce, make available,
|
||||
modify, display, perform, distribute, and otherwise exploit its
|
||||
Contributions, either on an unmodified basis, with Modifications, or as
|
||||
part of a Larger Work; and
|
||||
|
||||
b. under Patent Claims of such Contributor to make, use, sell, offer for
|
||||
sale, have made, import, and otherwise transfer either its Contributions
|
||||
or its Contributor Version.
|
||||
|
||||
2.2. Effective Date
|
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution become
|
||||
effective for each Contribution on the date the Contributor first distributes
|
||||
such Contribution.
|
||||
|
||||
2.3. Limitations on Grant Scope
|
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under this
|
||||
License. No additional rights or licenses will be implied from the distribution
|
||||
or licensing of Covered Software under this License. Notwithstanding Section
|
||||
2.1(b) above, no patent license is granted by a Contributor:
|
||||
|
||||
a. for any code that a Contributor has removed from Covered Software; or
|
||||
|
||||
b. for infringements caused by: (i) Your and any other third party’s
|
||||
modifications of Covered Software, or (ii) the combination of its
|
||||
Contributions with other software (except as part of its Contributor
|
||||
Version); or
|
||||
|
||||
c. under Patent Claims infringed by Covered Software in the absence of its
|
||||
Contributions.
|
||||
|
||||
This License does not grant any rights in the trademarks, service marks, or
|
||||
logos of any Contributor (except as may be necessary to comply with the
|
||||
notice requirements in Section 3.4).
|
||||
|
||||
2.4. Subsequent Licenses
|
||||
|
||||
No Contributor makes additional grants as a result of Your choice to
|
||||
distribute the Covered Software under a subsequent version of this License
|
||||
(see Section 10.2) or under the terms of a Secondary License (if permitted
|
||||
under the terms of Section 3.3).
|
||||
|
||||
2.5. Representation
|
||||
|
||||
Each Contributor represents that the Contributor believes its Contributions
|
||||
are its original creation(s) or it has sufficient rights to grant the
|
||||
rights to its Contributions conveyed by this License.
|
||||
|
||||
2.6. Fair Use
|
||||
|
||||
This License is not intended to limit any rights You have under applicable
|
||||
copyright doctrines of fair use, fair dealing, or other equivalents.
|
||||
|
||||
2.7. Conditions
|
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in
|
||||
Section 2.1.
|
||||
|
||||
|
||||
3. Responsibilities
|
||||
|
||||
3.1. Distribution of Source Form
|
||||
|
||||
All distribution of Covered Software in Source Code Form, including any
|
||||
Modifications that You create or to which You contribute, must be under the
|
||||
terms of this License. You must inform recipients that the Source Code Form
|
||||
of the Covered Software is governed by the terms of this License, and how
|
||||
they can obtain a copy of this License. You may not attempt to alter or
|
||||
restrict the recipients’ rights in the Source Code Form.
|
||||
|
||||
3.2. Distribution of Executable Form
|
||||
|
||||
If You distribute Covered Software in Executable Form then:
|
||||
|
||||
a. such Covered Software must also be made available in Source Code Form,
|
||||
as described in Section 3.1, and You must inform recipients of the
|
||||
Executable Form how they can obtain a copy of such Source Code Form by
|
||||
reasonable means in a timely manner, at a charge no more than the cost
|
||||
of distribution to the recipient; and
|
||||
|
||||
b. You may distribute such Executable Form under the terms of this License,
|
||||
or sublicense it under different terms, provided that the license for
|
||||
the Executable Form does not attempt to limit or alter the recipients’
|
||||
rights in the Source Code Form under this License.
|
||||
|
||||
3.3. Distribution of a Larger Work
|
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice,
|
||||
provided that You also comply with the requirements of this License for the
|
||||
Covered Software. If the Larger Work is a combination of Covered Software
|
||||
with a work governed by one or more Secondary Licenses, and the Covered
|
||||
Software is not Incompatible With Secondary Licenses, this License permits
|
||||
You to additionally distribute such Covered Software under the terms of
|
||||
such Secondary License(s), so that the recipient of the Larger Work may, at
|
||||
their option, further distribute the Covered Software under the terms of
|
||||
either this License or such Secondary License(s).
|
||||
|
||||
3.4. Notices
|
||||
|
||||
You may not remove or alter the substance of any license notices (including
|
||||
copyright notices, patent notices, disclaimers of warranty, or limitations
|
||||
of liability) contained within the Source Code Form of the Covered
|
||||
Software, except that You may alter any license notices to the extent
|
||||
required to remedy known factual inaccuracies.
|
||||
|
||||
3.5. Application of Additional Terms
|
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support,
|
||||
indemnity or liability obligations to one or more recipients of Covered
|
||||
Software. However, You may do so only on Your own behalf, and not on behalf
|
||||
of any Contributor. You must make it absolutely clear that any such
|
||||
warranty, support, indemnity, or liability obligation is offered by You
|
||||
alone, and You hereby agree to indemnify every Contributor for any
|
||||
liability incurred by such Contributor as a result of warranty, support,
|
||||
indemnity or liability terms You offer. You may include additional
|
||||
disclaimers of warranty and limitations of liability specific to any
|
||||
jurisdiction.
|
||||
|
||||
4. Inability to Comply Due to Statute or Regulation
|
||||
|
||||
If it is impossible for You to comply with any of the terms of this License
|
||||
with respect to some or all of the Covered Software due to statute, judicial
|
||||
order, or regulation then You must: (a) comply with the terms of this License
|
||||
to the maximum extent possible; and (b) describe the limitations and the code
|
||||
they affect. Such description must be placed in a text file included with all
|
||||
distributions of the Covered Software under this License. Except to the
|
||||
extent prohibited by statute or regulation, such description must be
|
||||
sufficiently detailed for a recipient of ordinary skill to be able to
|
||||
understand it.
|
||||
|
||||
5. Termination
|
||||
|
||||
5.1. The rights granted under this License will terminate automatically if You
|
||||
fail to comply with any of its terms. However, if You become compliant,
|
||||
then the rights granted under this License from a particular Contributor
|
||||
are reinstated (a) provisionally, unless and until such Contributor
|
||||
explicitly and finally terminates Your grants, and (b) on an ongoing basis,
|
||||
if such Contributor fails to notify You of the non-compliance by some
|
||||
reasonable means prior to 60 days after You have come back into compliance.
|
||||
Moreover, Your grants from a particular Contributor are reinstated on an
|
||||
ongoing basis if such Contributor notifies You of the non-compliance by
|
||||
some reasonable means, this is the first time You have received notice of
|
||||
non-compliance with this License from such Contributor, and You become
|
||||
compliant prior to 30 days after Your receipt of the notice.
|
||||
|
||||
5.2. If You initiate litigation against any entity by asserting a patent
|
||||
infringement claim (excluding declaratory judgment actions, counter-claims,
|
||||
and cross-claims) alleging that a Contributor Version directly or
|
||||
indirectly infringes any patent, then the rights granted to You by any and
|
||||
all Contributors for the Covered Software under Section 2.1 of this License
|
||||
shall terminate.
|
||||
|
||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user
|
||||
license agreements (excluding distributors and resellers) which have been
|
||||
validly granted by You or Your distributors under this License prior to
|
||||
termination shall survive termination.
|
||||
|
||||
6. Disclaimer of Warranty
|
||||
|
||||
Covered Software is provided under this License on an “as is” basis, without
|
||||
warranty of any kind, either expressed, implied, or statutory, including,
|
||||
without limitation, warranties that the Covered Software is free of defects,
|
||||
merchantable, fit for a particular purpose or non-infringing. The entire
|
||||
risk as to the quality and performance of the Covered Software is with You.
|
||||
Should any Covered Software prove defective in any respect, You (not any
|
||||
Contributor) assume the cost of any necessary servicing, repair, or
|
||||
correction. This disclaimer of warranty constitutes an essential part of this
|
||||
License. No use of any Covered Software is authorized under this License
|
||||
except under this disclaimer.
|
||||
|
||||
7. Limitation of Liability
|
||||
|
||||
Under no circumstances and under no legal theory, whether tort (including
|
||||
negligence), contract, or otherwise, shall any Contributor, or anyone who
|
||||
distributes Covered Software as permitted above, be liable to You for any
|
||||
direct, indirect, special, incidental, or consequential damages of any
|
||||
character including, without limitation, damages for lost profits, loss of
|
||||
goodwill, work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses, even if such party shall have been
|
||||
informed of the possibility of such damages. This limitation of liability
|
||||
shall not apply to liability for death or personal injury resulting from such
|
||||
party’s negligence to the extent applicable law prohibits such limitation.
|
||||
Some jurisdictions do not allow the exclusion or limitation of incidental or
|
||||
consequential damages, so this exclusion and limitation may not apply to You.
|
||||
|
||||
8. Litigation
|
||||
|
||||
Any litigation relating to this License may be brought only in the courts of
|
||||
a jurisdiction where the defendant maintains its principal place of business
|
||||
and such litigation shall be governed by laws of that jurisdiction, without
|
||||
reference to its conflict-of-law provisions. Nothing in this Section shall
|
||||
prevent a party’s ability to bring cross-claims or counter-claims.
|
||||
|
||||
9. Miscellaneous
|
||||
|
||||
This License represents the complete agreement concerning the subject matter
|
||||
hereof. If any provision of this License is held to be unenforceable, such
|
||||
provision shall be reformed only to the extent necessary to make it
|
||||
enforceable. Any law or regulation which provides that the language of a
|
||||
contract shall be construed against the drafter shall not be used to construe
|
||||
this License against a Contributor.
|
||||
|
||||
|
||||
10. Versions of the License
|
||||
|
||||
10.1. New Versions
|
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section
|
||||
10.3, no one other than the license steward has the right to modify or
|
||||
publish new versions of this License. Each version will be given a
|
||||
distinguishing version number.
|
||||
|
||||
10.2. Effect of New Versions
|
||||
|
||||
You may distribute the Covered Software under the terms of the version of
|
||||
the License under which You originally received the Covered Software, or
|
||||
under the terms of any subsequent version published by the license
|
||||
steward.
|
||||
|
||||
10.3. Modified Versions
|
||||
|
||||
If you create software not governed by this License, and you want to
|
||||
create a new license for such software, you may create and use a modified
|
||||
version of this License if you rename the license and remove any
|
||||
references to the name of the license steward (except to note that such
|
||||
modified license differs from this License).
|
||||
|
||||
10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses
|
||||
If You choose to distribute Source Code Form that is Incompatible With
|
||||
Secondary Licenses under the terms of this version of the License, the
|
||||
notice described in Exhibit B of this License must be attached.
|
||||
|
||||
Exhibit A - Source Code Form License Notice
|
||||
|
||||
This Source Code Form is subject to the
|
||||
terms of the Mozilla Public License, v.
|
||||
2.0. If a copy of the MPL was not
|
||||
distributed with this file, You can
|
||||
obtain one at
|
||||
http://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular file, then
|
||||
You may include the notice in a location (such as a LICENSE file in a relevant
|
||||
directory) where a recipient would be likely to look for such a notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
||||
|
||||
Exhibit B - “Incompatible With Secondary Licenses” Notice
|
||||
|
||||
This Source Code Form is “Incompatible
|
||||
With Secondary Licenses”, as defined by
|
||||
the Mozilla Public License, v. 2.0.
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
# errwrap
|
||||
|
||||
`errwrap` is a package for Go that formalizes the pattern of wrapping errors
|
||||
and checking if an error contains another error.
|
||||
|
||||
There is a common pattern in Go of taking a returned `error` value and
|
||||
then wrapping it (such as with `fmt.Errorf`) before returning it. The problem
|
||||
with this pattern is that you completely lose the original `error` structure.
|
||||
|
||||
Arguably the _correct_ approach is that you should make a custom structure
|
||||
implementing the `error` interface, and have the original error as a field
|
||||
on that structure, such [as this example](http://golang.org/pkg/os/#PathError).
|
||||
This is a good approach, but you have to know the entire chain of possible
|
||||
rewrapping that happens, when you might just care about one.
|
||||
|
||||
`errwrap` formalizes this pattern (it doesn't matter what approach you use
|
||||
above) by giving a single interface for wrapping errors, checking if a specific
|
||||
error is wrapped, and extracting that error.
|
||||
|
||||
## Installation and Docs
|
||||
|
||||
Install using `go get github.com/hashicorp/errwrap`.
|
||||
|
||||
Full documentation is available at
|
||||
http://godoc.org/github.com/hashicorp/errwrap
|
||||
|
||||
## Usage
|
||||
|
||||
#### Basic Usage
|
||||
|
||||
Below is a very basic example of its usage:
|
||||
|
||||
```go
|
||||
// A function that always returns an error, but wraps it, like a real
|
||||
// function might.
|
||||
func tryOpen() error {
|
||||
_, err := os.Open("/i/dont/exist")
|
||||
if err != nil {
|
||||
return errwrap.Wrapf("Doesn't exist: {{err}}", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
err := tryOpen()
|
||||
|
||||
// We can use the Contains helpers to check if an error contains
|
||||
// another error. It is safe to do this with a nil error, or with
|
||||
// an error that doesn't even use the errwrap package.
|
||||
if errwrap.Contains(err, ErrNotExist) {
|
||||
// Do something
|
||||
}
|
||||
if errwrap.ContainsType(err, new(os.PathError)) {
|
||||
// Do something
|
||||
}
|
||||
|
||||
// Or we can use the associated `Get` functions to just extract
|
||||
// a specific error. This would return nil if that specific error doesn't
|
||||
// exist.
|
||||
perr := errwrap.GetType(err, new(os.PathError))
|
||||
}
|
||||
```
|
||||
|
||||
#### Custom Types
|
||||
|
||||
If you're already making custom types that properly wrap errors, then
|
||||
you can get all the functionality of `errwraps.Contains` and such by
|
||||
implementing the `Wrapper` interface with just one function. Example:
|
||||
|
||||
```go
|
||||
type AppError {
|
||||
Code ErrorCode
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *AppError) WrappedErrors() []error {
|
||||
return []error{e.Err}
|
||||
}
|
||||
```
|
||||
|
||||
Now this works:
|
||||
|
||||
```go
|
||||
err := &AppError{Err: fmt.Errorf("an error")}
|
||||
if errwrap.ContainsType(err, fmt.Errorf("")) {
|
||||
// This will work!
|
||||
}
|
||||
```
|
|
@ -0,0 +1,169 @@
|
|||
// Package errwrap implements methods to formalize error wrapping in Go.
|
||||
//
|
||||
// All of the top-level functions that take an `error` are built to be able
|
||||
// to take any error, not just wrapped errors. This allows you to use errwrap
|
||||
// without having to type-check and type-cast everywhere.
|
||||
package errwrap
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// WalkFunc is the callback called for Walk.
|
||||
type WalkFunc func(error)
|
||||
|
||||
// Wrapper is an interface that can be implemented by custom types to
|
||||
// have all the Contains, Get, etc. functions in errwrap work.
|
||||
//
|
||||
// When Walk reaches a Wrapper, it will call the callback for every
|
||||
// wrapped error in addition to the wrapper itself. Since all the top-level
|
||||
// functions in errwrap use Walk, this means that all those functions work
|
||||
// with your custom type.
|
||||
type Wrapper interface {
|
||||
WrappedErrors() []error
|
||||
}
|
||||
|
||||
// Wrap defines that outer wraps inner, returning an error type that
|
||||
// can be cleanly used with the other methods in this package, such as
|
||||
// Contains, GetAll, etc.
|
||||
//
|
||||
// This function won't modify the error message at all (the outer message
|
||||
// will be used).
|
||||
func Wrap(outer, inner error) error {
|
||||
return &wrappedError{
|
||||
Outer: outer,
|
||||
Inner: inner,
|
||||
}
|
||||
}
|
||||
|
||||
// Wrapf wraps an error with a formatting message. This is similar to using
|
||||
// `fmt.Errorf` to wrap an error. If you're using `fmt.Errorf` to wrap
|
||||
// errors, you should replace it with this.
|
||||
//
|
||||
// format is the format of the error message. The string '{{err}}' will
|
||||
// be replaced with the original error message.
|
||||
func Wrapf(format string, err error) error {
|
||||
outerMsg := "<nil>"
|
||||
if err != nil {
|
||||
outerMsg = err.Error()
|
||||
}
|
||||
|
||||
outer := errors.New(strings.Replace(
|
||||
format, "{{err}}", outerMsg, -1))
|
||||
|
||||
return Wrap(outer, err)
|
||||
}
|
||||
|
||||
// Contains checks if the given error contains an error with the
|
||||
// message msg. If err is not a wrapped error, this will always return
|
||||
// false unless the error itself happens to match this msg.
|
||||
func Contains(err error, msg string) bool {
|
||||
return len(GetAll(err, msg)) > 0
|
||||
}
|
||||
|
||||
// ContainsType checks if the given error contains an error with
|
||||
// the same concrete type as v. If err is not a wrapped error, this will
|
||||
// check the err itself.
|
||||
func ContainsType(err error, v interface{}) bool {
|
||||
return len(GetAllType(err, v)) > 0
|
||||
}
|
||||
|
||||
// Get is the same as GetAll but returns the deepest matching error.
|
||||
func Get(err error, msg string) error {
|
||||
es := GetAll(err, msg)
|
||||
if len(es) > 0 {
|
||||
return es[len(es)-1]
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetType is the same as GetAllType but returns the deepest matching error.
|
||||
func GetType(err error, v interface{}) error {
|
||||
es := GetAllType(err, v)
|
||||
if len(es) > 0 {
|
||||
return es[len(es)-1]
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetAll gets all the errors that might be wrapped in err with the
|
||||
// given message. The order of the errors is such that the outermost
|
||||
// matching error (the most recent wrap) is index zero, and so on.
|
||||
func GetAll(err error, msg string) []error {
|
||||
var result []error
|
||||
|
||||
Walk(err, func(err error) {
|
||||
if err.Error() == msg {
|
||||
result = append(result, err)
|
||||
}
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// GetAllType gets all the errors that are the same type as v.
|
||||
//
|
||||
// The order of the return value is the same as described in GetAll.
|
||||
func GetAllType(err error, v interface{}) []error {
|
||||
var result []error
|
||||
|
||||
var search string
|
||||
if v != nil {
|
||||
search = reflect.TypeOf(v).String()
|
||||
}
|
||||
Walk(err, func(err error) {
|
||||
var needle string
|
||||
if err != nil {
|
||||
needle = reflect.TypeOf(err).String()
|
||||
}
|
||||
|
||||
if needle == search {
|
||||
result = append(result, err)
|
||||
}
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Walk walks all the wrapped errors in err and calls the callback. If
|
||||
// err isn't a wrapped error, this will be called once for err. If err
|
||||
// is a wrapped error, the callback will be called for both the wrapper
|
||||
// that implements error as well as the wrapped error itself.
|
||||
func Walk(err error, cb WalkFunc) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch e := err.(type) {
|
||||
case *wrappedError:
|
||||
cb(e.Outer)
|
||||
Walk(e.Inner, cb)
|
||||
case Wrapper:
|
||||
cb(err)
|
||||
|
||||
for _, err := range e.WrappedErrors() {
|
||||
Walk(err, cb)
|
||||
}
|
||||
default:
|
||||
cb(err)
|
||||
}
|
||||
}
|
||||
|
||||
// wrappedError is an implementation of error that has both the
|
||||
// outer and inner errors.
|
||||
type wrappedError struct {
|
||||
Outer error
|
||||
Inner error
|
||||
}
|
||||
|
||||
func (w *wrappedError) Error() string {
|
||||
return w.Outer.Error()
|
||||
}
|
||||
|
||||
func (w *wrappedError) WrappedErrors() []error {
|
||||
return []error{w.Outer, w.Inner}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
sudo: false
|
||||
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.x
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
|
||||
script: make test testrace
|
|
@ -0,0 +1,353 @@
|
|||
Mozilla Public License, version 2.0
|
||||
|
||||
1. Definitions
|
||||
|
||||
1.1. “Contributor”
|
||||
|
||||
means each individual or legal entity that creates, contributes to the
|
||||
creation of, or owns Covered Software.
|
||||
|
||||
1.2. “Contributor Version”
|
||||
|
||||
means the combination of the Contributions of others (if any) used by a
|
||||
Contributor and that particular Contributor’s Contribution.
|
||||
|
||||
1.3. “Contribution”
|
||||
|
||||
means Covered Software of a particular Contributor.
|
||||
|
||||
1.4. “Covered Software”
|
||||
|
||||
means Source Code Form to which the initial Contributor has attached the
|
||||
notice in Exhibit A, the Executable Form of such Source Code Form, and
|
||||
Modifications of such Source Code Form, in each case including portions
|
||||
thereof.
|
||||
|
||||
1.5. “Incompatible With Secondary Licenses”
|
||||
means
|
||||
|
||||
a. that the initial Contributor has attached the notice described in
|
||||
Exhibit B to the Covered Software; or
|
||||
|
||||
b. that the Covered Software was made available under the terms of version
|
||||
1.1 or earlier of the License, but not also under the terms of a
|
||||
Secondary License.
|
||||
|
||||
1.6. “Executable Form”
|
||||
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
1.7. “Larger Work”
|
||||
|
||||
means a work that combines Covered Software with other material, in a separate
|
||||
file or files, that is not Covered Software.
|
||||
|
||||
1.8. “License”
|
||||
|
||||
means this document.
|
||||
|
||||
1.9. “Licensable”
|
||||
|
||||
means having the right to grant, to the maximum extent possible, whether at the
|
||||
time of the initial grant or subsequently, any and all of the rights conveyed by
|
||||
this License.
|
||||
|
||||
1.10. “Modifications”
|
||||
|
||||
means any of the following:
|
||||
|
||||
a. any file in Source Code Form that results from an addition to, deletion
|
||||
from, or modification of the contents of Covered Software; or
|
||||
|
||||
b. any new file in Source Code Form that contains any Covered Software.
|
||||
|
||||
1.11. “Patent Claims” of a Contributor
|
||||
|
||||
means any patent claim(s), including without limitation, method, process,
|
||||
and apparatus claims, in any patent Licensable by such Contributor that
|
||||
would be infringed, but for the grant of the License, by the making,
|
||||
using, selling, offering for sale, having made, import, or transfer of
|
||||
either its Contributions or its Contributor Version.
|
||||
|
||||
1.12. “Secondary License”
|
||||
|
||||
means either the GNU General Public License, Version 2.0, the GNU Lesser
|
||||
General Public License, Version 2.1, the GNU Affero General Public
|
||||
License, Version 3.0, or any later versions of those licenses.
|
||||
|
||||
1.13. “Source Code Form”
|
||||
|
||||
means the form of the work preferred for making modifications.
|
||||
|
||||
1.14. “You” (or “Your”)
|
||||
|
||||
means an individual or a legal entity exercising rights under this
|
||||
License. For legal entities, “You” includes any entity that controls, is
|
||||
controlled by, or is under common control with You. For purposes of this
|
||||
definition, “control” means (a) the power, direct or indirect, to cause
|
||||
the direction or management of such entity, whether by contract or
|
||||
otherwise, or (b) ownership of more than fifty percent (50%) of the
|
||||
outstanding shares or beneficial ownership of such entity.
|
||||
|
||||
|
||||
2. License Grants and Conditions
|
||||
|
||||
2.1. Grants
|
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
a. under intellectual property rights (other than patent or trademark)
|
||||
Licensable by such Contributor to use, reproduce, make available,
|
||||
modify, display, perform, distribute, and otherwise exploit its
|
||||
Contributions, either on an unmodified basis, with Modifications, or as
|
||||
part of a Larger Work; and
|
||||
|
||||
b. under Patent Claims of such Contributor to make, use, sell, offer for
|
||||
sale, have made, import, and otherwise transfer either its Contributions
|
||||
or its Contributor Version.
|
||||
|
||||
2.2. Effective Date
|
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution become
|
||||
effective for each Contribution on the date the Contributor first distributes
|
||||
such Contribution.
|
||||
|
||||
2.3. Limitations on Grant Scope
|
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under this
|
||||
License. No additional rights or licenses will be implied from the distribution
|
||||
or licensing of Covered Software under this License. Notwithstanding Section
|
||||
2.1(b) above, no patent license is granted by a Contributor:
|
||||
|
||||
a. for any code that a Contributor has removed from Covered Software; or
|
||||
|
||||
b. for infringements caused by: (i) Your and any other third party’s
|
||||
modifications of Covered Software, or (ii) the combination of its
|
||||
Contributions with other software (except as part of its Contributor
|
||||
Version); or
|
||||
|
||||
c. under Patent Claims infringed by Covered Software in the absence of its
|
||||
Contributions.
|
||||
|
||||
This License does not grant any rights in the trademarks, service marks, or
|
||||
logos of any Contributor (except as may be necessary to comply with the
|
||||
notice requirements in Section 3.4).
|
||||
|
||||
2.4. Subsequent Licenses
|
||||
|
||||
No Contributor makes additional grants as a result of Your choice to
|
||||
distribute the Covered Software under a subsequent version of this License
|
||||
(see Section 10.2) or under the terms of a Secondary License (if permitted
|
||||
under the terms of Section 3.3).
|
||||
|
||||
2.5. Representation
|
||||
|
||||
Each Contributor represents that the Contributor believes its Contributions
|
||||
are its original creation(s) or it has sufficient rights to grant the
|
||||
rights to its Contributions conveyed by this License.
|
||||
|
||||
2.6. Fair Use
|
||||
|
||||
This License is not intended to limit any rights You have under applicable
|
||||
copyright doctrines of fair use, fair dealing, or other equivalents.
|
||||
|
||||
2.7. Conditions
|
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in
|
||||
Section 2.1.
|
||||
|
||||
|
||||
3. Responsibilities
|
||||
|
||||
3.1. Distribution of Source Form
|
||||
|
||||
All distribution of Covered Software in Source Code Form, including any
|
||||
Modifications that You create or to which You contribute, must be under the
|
||||
terms of this License. You must inform recipients that the Source Code Form
|
||||
of the Covered Software is governed by the terms of this License, and how
|
||||
they can obtain a copy of this License. You may not attempt to alter or
|
||||
restrict the recipients’ rights in the Source Code Form.
|
||||
|
||||
3.2. Distribution of Executable Form
|
||||
|
||||
If You distribute Covered Software in Executable Form then:
|
||||
|
||||
a. such Covered Software must also be made available in Source Code Form,
|
||||
as described in Section 3.1, and You must inform recipients of the
|
||||
Executable Form how they can obtain a copy of such Source Code Form by
|
||||
reasonable means in a timely manner, at a charge no more than the cost
|
||||
of distribution to the recipient; and
|
||||
|
||||
b. You may distribute such Executable Form under the terms of this License,
|
||||
or sublicense it under different terms, provided that the license for
|
||||
the Executable Form does not attempt to limit or alter the recipients’
|
||||
rights in the Source Code Form under this License.
|
||||
|
||||
3.3. Distribution of a Larger Work
|
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice,
|
||||
provided that You also comply with the requirements of this License for the
|
||||
Covered Software. If the Larger Work is a combination of Covered Software
|
||||
with a work governed by one or more Secondary Licenses, and the Covered
|
||||
Software is not Incompatible With Secondary Licenses, this License permits
|
||||
You to additionally distribute such Covered Software under the terms of
|
||||
such Secondary License(s), so that the recipient of the Larger Work may, at
|
||||
their option, further distribute the Covered Software under the terms of
|
||||
either this License or such Secondary License(s).
|
||||
|
||||
3.4. Notices
|
||||
|
||||
You may not remove or alter the substance of any license notices (including
|
||||
copyright notices, patent notices, disclaimers of warranty, or limitations
|
||||
of liability) contained within the Source Code Form of the Covered
|
||||
Software, except that You may alter any license notices to the extent
|
||||
required to remedy known factual inaccuracies.
|
||||
|
||||
3.5. Application of Additional Terms
|
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support,
|
||||
indemnity or liability obligations to one or more recipients of Covered
|
||||
Software. However, You may do so only on Your own behalf, and not on behalf
|
||||
of any Contributor. You must make it absolutely clear that any such
|
||||
warranty, support, indemnity, or liability obligation is offered by You
|
||||
alone, and You hereby agree to indemnify every Contributor for any
|
||||
liability incurred by such Contributor as a result of warranty, support,
|
||||
indemnity or liability terms You offer. You may include additional
|
||||
disclaimers of warranty and limitations of liability specific to any
|
||||
jurisdiction.
|
||||
|
||||
4. Inability to Comply Due to Statute or Regulation
|
||||
|
||||
If it is impossible for You to comply with any of the terms of this License
|
||||
with respect to some or all of the Covered Software due to statute, judicial
|
||||
order, or regulation then You must: (a) comply with the terms of this License
|
||||
to the maximum extent possible; and (b) describe the limitations and the code
|
||||
they affect. Such description must be placed in a text file included with all
|
||||
distributions of the Covered Software under this License. Except to the
|
||||
extent prohibited by statute or regulation, such description must be
|
||||
sufficiently detailed for a recipient of ordinary skill to be able to
|
||||
understand it.
|
||||
|
||||
5. Termination
|
||||
|
||||
5.1. The rights granted under this License will terminate automatically if You
|
||||
fail to comply with any of its terms. However, if You become compliant,
|
||||
then the rights granted under this License from a particular Contributor
|
||||
are reinstated (a) provisionally, unless and until such Contributor
|
||||
explicitly and finally terminates Your grants, and (b) on an ongoing basis,
|
||||
if such Contributor fails to notify You of the non-compliance by some
|
||||
reasonable means prior to 60 days after You have come back into compliance.
|
||||
Moreover, Your grants from a particular Contributor are reinstated on an
|
||||
ongoing basis if such Contributor notifies You of the non-compliance by
|
||||
some reasonable means, this is the first time You have received notice of
|
||||
non-compliance with this License from such Contributor, and You become
|
||||
compliant prior to 30 days after Your receipt of the notice.
|
||||
|
||||
5.2. If You initiate litigation against any entity by asserting a patent
|
||||
infringement claim (excluding declaratory judgment actions, counter-claims,
|
||||
and cross-claims) alleging that a Contributor Version directly or
|
||||
indirectly infringes any patent, then the rights granted to You by any and
|
||||
all Contributors for the Covered Software under Section 2.1 of this License
|
||||
shall terminate.
|
||||
|
||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user
|
||||
license agreements (excluding distributors and resellers) which have been
|
||||
validly granted by You or Your distributors under this License prior to
|
||||
termination shall survive termination.
|
||||
|
||||
6. Disclaimer of Warranty
|
||||
|
||||
Covered Software is provided under this License on an “as is” basis, without
|
||||
warranty of any kind, either expressed, implied, or statutory, including,
|
||||
without limitation, warranties that the Covered Software is free of defects,
|
||||
merchantable, fit for a particular purpose or non-infringing. The entire
|
||||
risk as to the quality and performance of the Covered Software is with You.
|
||||
Should any Covered Software prove defective in any respect, You (not any
|
||||
Contributor) assume the cost of any necessary servicing, repair, or
|
||||
correction. This disclaimer of warranty constitutes an essential part of this
|
||||
License. No use of any Covered Software is authorized under this License
|
||||
except under this disclaimer.
|
||||
|
||||
7. Limitation of Liability
|
||||
|
||||
Under no circumstances and under no legal theory, whether tort (including
|
||||
negligence), contract, or otherwise, shall any Contributor, or anyone who
|
||||
distributes Covered Software as permitted above, be liable to You for any
|
||||
direct, indirect, special, incidental, or consequential damages of any
|
||||
character including, without limitation, damages for lost profits, loss of
|
||||
goodwill, work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses, even if such party shall have been
|
||||
informed of the possibility of such damages. This limitation of liability
|
||||
shall not apply to liability for death or personal injury resulting from such
|
||||
party’s negligence to the extent applicable law prohibits such limitation.
|
||||
Some jurisdictions do not allow the exclusion or limitation of incidental or
|
||||
consequential damages, so this exclusion and limitation may not apply to You.
|
||||
|
||||
8. Litigation
|
||||
|
||||
Any litigation relating to this License may be brought only in the courts of
|
||||
a jurisdiction where the defendant maintains its principal place of business
|
||||
and such litigation shall be governed by laws of that jurisdiction, without
|
||||
reference to its conflict-of-law provisions. Nothing in this Section shall
|
||||
prevent a party’s ability to bring cross-claims or counter-claims.
|
||||
|
||||
9. Miscellaneous
|
||||
|
||||
This License represents the complete agreement concerning the subject matter
|
||||
hereof. If any provision of this License is held to be unenforceable, such
|
||||
provision shall be reformed only to the extent necessary to make it
|
||||
enforceable. Any law or regulation which provides that the language of a
|
||||
contract shall be construed against the drafter shall not be used to construe
|
||||
this License against a Contributor.
|
||||
|
||||
|
||||
10. Versions of the License
|
||||
|
||||
10.1. New Versions
|
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section
|
||||
10.3, no one other than the license steward has the right to modify or
|
||||
publish new versions of this License. Each version will be given a
|
||||
distinguishing version number.
|
||||
|
||||
10.2. Effect of New Versions
|
||||
|
||||
You may distribute the Covered Software under the terms of the version of
|
||||
the License under which You originally received the Covered Software, or
|
||||
under the terms of any subsequent version published by the license
|
||||
steward.
|
||||
|
||||
10.3. Modified Versions
|
||||
|
||||
If you create software not governed by this License, and you want to
|
||||
create a new license for such software, you may create and use a modified
|
||||
version of this License if you rename the license and remove any
|
||||
references to the name of the license steward (except to note that such
|
||||
modified license differs from this License).
|
||||
|
||||
10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses
|
||||
If You choose to distribute Source Code Form that is Incompatible With
|
||||
Secondary Licenses under the terms of this version of the License, the
|
||||
notice described in Exhibit B of this License must be attached.
|
||||
|
||||
Exhibit A - Source Code Form License Notice
|
||||
|
||||
This Source Code Form is subject to the
|
||||
terms of the Mozilla Public License, v.
|
||||
2.0. If a copy of the MPL was not
|
||||
distributed with this file, You can
|
||||
obtain one at
|
||||
http://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular file, then
|
||||
You may include the notice in a location (such as a LICENSE file in a relevant
|
||||
directory) where a recipient would be likely to look for such a notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
||||
|
||||
Exhibit B - “Incompatible With Secondary Licenses” Notice
|
||||
|
||||
This Source Code Form is “Incompatible
|
||||
With Secondary Licenses”, as defined by
|
||||
the Mozilla Public License, v. 2.0.
|
|
@ -0,0 +1,31 @@
|
|||
TEST?=./...
|
||||
|
||||
default: test
|
||||
|
||||
# test runs the test suite and vets the code.
|
||||
test: generate
|
||||
@echo "==> Running tests..."
|
||||
@go list $(TEST) \
|
||||
| grep -v "/vendor/" \
|
||||
| xargs -n1 go test -timeout=60s -parallel=10 ${TESTARGS}
|
||||
|
||||
# testrace runs the race checker
|
||||
testrace: generate
|
||||
@echo "==> Running tests (race)..."
|
||||
@go list $(TEST) \
|
||||
| grep -v "/vendor/" \
|
||||
| xargs -n1 go test -timeout=60s -race ${TESTARGS}
|
||||
|
||||
# updatedeps installs all the dependencies needed to run and build.
|
||||
updatedeps:
|
||||
@sh -c "'${CURDIR}/scripts/deps.sh' '${NAME}'"
|
||||
|
||||
# generate runs `go generate` to build the dynamically generated source files.
|
||||
generate:
|
||||
@echo "==> Generating..."
|
||||
@find . -type f -name '.DS_Store' -delete
|
||||
@go list ./... \
|
||||
| grep -v "/vendor/" \
|
||||
| xargs -n1 go generate
|
||||
|
||||
.PHONY: default test testrace updatedeps generate
|
|
@ -0,0 +1,97 @@
|
|||
# go-multierror
|
||||
|
||||
[![Build Status](http://img.shields.io/travis/hashicorp/go-multierror.svg?style=flat-square)][travis]
|
||||
[![Go Documentation](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)][godocs]
|
||||
|
||||
[travis]: https://travis-ci.org/hashicorp/go-multierror
|
||||
[godocs]: https://godoc.org/github.com/hashicorp/go-multierror
|
||||
|
||||
`go-multierror` is a package for Go that provides a mechanism for
|
||||
representing a list of `error` values as a single `error`.
|
||||
|
||||
This allows a function in Go to return an `error` that might actually
|
||||
be a list of errors. If the caller knows this, they can unwrap the
|
||||
list and access the errors. If the caller doesn't know, the error
|
||||
formats to a nice human-readable format.
|
||||
|
||||
`go-multierror` implements the
|
||||
[errwrap](https://github.com/hashicorp/errwrap) interface so that it can
|
||||
be used with that library, as well.
|
||||
|
||||
## Installation and Docs
|
||||
|
||||
Install using `go get github.com/hashicorp/go-multierror`.
|
||||
|
||||
Full documentation is available at
|
||||
http://godoc.org/github.com/hashicorp/go-multierror
|
||||
|
||||
## Usage
|
||||
|
||||
go-multierror is easy to use and purposely built to be unobtrusive in
|
||||
existing Go applications/libraries that may not be aware of it.
|
||||
|
||||
**Building a list of errors**
|
||||
|
||||
The `Append` function is used to create a list of errors. This function
|
||||
behaves a lot like the Go built-in `append` function: it doesn't matter
|
||||
if the first argument is nil, a `multierror.Error`, or any other `error`,
|
||||
the function behaves as you would expect.
|
||||
|
||||
```go
|
||||
var result error
|
||||
|
||||
if err := step1(); err != nil {
|
||||
result = multierror.Append(result, err)
|
||||
}
|
||||
if err := step2(); err != nil {
|
||||
result = multierror.Append(result, err)
|
||||
}
|
||||
|
||||
return result
|
||||
```
|
||||
|
||||
**Customizing the formatting of the errors**
|
||||
|
||||
By specifying a custom `ErrorFormat`, you can customize the format
|
||||
of the `Error() string` function:
|
||||
|
||||
```go
|
||||
var result *multierror.Error
|
||||
|
||||
// ... accumulate errors here, maybe using Append
|
||||
|
||||
if result != nil {
|
||||
result.ErrorFormat = func([]error) string {
|
||||
return "errors!"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Accessing the list of errors**
|
||||
|
||||
`multierror.Error` implements `error` so if the caller doesn't know about
|
||||
multierror, it will work just fine. But if you're aware a multierror might
|
||||
be returned, you can use type switches to access the list of errors:
|
||||
|
||||
```go
|
||||
if err := something(); err != nil {
|
||||
if merr, ok := err.(*multierror.Error); ok {
|
||||
// Use merr.Errors
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Returning a multierror only if there are errors**
|
||||
|
||||
If you build a `multierror.Error`, you can use the `ErrorOrNil` function
|
||||
to return an `error` implementation only if there are errors to return:
|
||||
|
||||
```go
|
||||
var result *multierror.Error
|
||||
|
||||
// ... accumulate errors here
|
||||
|
||||
// Return the `error` only if errors were added to the multierror, otherwise
|
||||
// return nil since there are no errors.
|
||||
return result.ErrorOrNil()
|
||||
```
|
|
@ -0,0 +1,41 @@
|
|||
package multierror
|
||||
|
||||
// Append is a helper function that will append more errors
|
||||
// onto an Error in order to create a larger multi-error.
|
||||
//
|
||||
// If err is not a multierror.Error, then it will be turned into
|
||||
// one. If any of the errs are multierr.Error, they will be flattened
|
||||
// one level into err.
|
||||
func Append(err error, errs ...error) *Error {
|
||||
switch err := err.(type) {
|
||||
case *Error:
|
||||
// Typed nils can reach here, so initialize if we are nil
|
||||
if err == nil {
|
||||
err = new(Error)
|
||||
}
|
||||
|
||||
// Go through each error and flatten
|
||||
for _, e := range errs {
|
||||
switch e := e.(type) {
|
||||
case *Error:
|
||||
if e != nil {
|
||||
err.Errors = append(err.Errors, e.Errors...)
|
||||
}
|
||||
default:
|
||||
if e != nil {
|
||||
err.Errors = append(err.Errors, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
default:
|
||||
newErrs := make([]error, 0, len(errs)+1)
|
||||
if err != nil {
|
||||
newErrs = append(newErrs, err)
|
||||
}
|
||||
newErrs = append(newErrs, errs...)
|
||||
|
||||
return Append(&Error{}, newErrs...)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package multierror
|
||||
|
||||
// Flatten flattens the given error, merging any *Errors together into
|
||||
// a single *Error.
|
||||
func Flatten(err error) error {
|
||||
// If it isn't an *Error, just return the error as-is
|
||||
if _, ok := err.(*Error); !ok {
|
||||
return err
|
||||
}
|
||||
|
||||
// Otherwise, make the result and flatten away!
|
||||
flatErr := new(Error)
|
||||
flatten(err, flatErr)
|
||||
return flatErr
|
||||
}
|
||||
|
||||
func flatten(err error, flatErr *Error) {
|
||||
switch err := err.(type) {
|
||||
case *Error:
|
||||
for _, e := range err.Errors {
|
||||
flatten(e, flatErr)
|
||||
}
|
||||
default:
|
||||
flatErr.Errors = append(flatErr.Errors, err)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package multierror
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ErrorFormatFunc is a function callback that is called by Error to
|
||||
// turn the list of errors into a string.
|
||||
type ErrorFormatFunc func([]error) string
|
||||
|
||||
// ListFormatFunc is a basic formatter that outputs the number of errors
|
||||
// that occurred along with a bullet point list of the errors.
|
||||
func ListFormatFunc(es []error) string {
|
||||
if len(es) == 1 {
|
||||
return fmt.Sprintf("1 error occurred:\n\n* %s", es[0])
|
||||
}
|
||||
|
||||
points := make([]string, len(es))
|
||||
for i, err := range es {
|
||||
points[i] = fmt.Sprintf("* %s", err)
|
||||
}
|
||||
|
||||
return fmt.Sprintf(
|
||||
"%d errors occurred:\n\n%s",
|
||||
len(es), strings.Join(points, "\n"))
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
package multierror
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Error is an error type to track multiple errors. This is used to
|
||||
// accumulate errors in cases and return them as a single "error".
|
||||
type Error struct {
|
||||
Errors []error
|
||||
ErrorFormat ErrorFormatFunc
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
fn := e.ErrorFormat
|
||||
if fn == nil {
|
||||
fn = ListFormatFunc
|
||||
}
|
||||
|
||||
return fn(e.Errors)
|
||||
}
|
||||
|
||||
// ErrorOrNil returns an error interface if this Error represents
|
||||
// a list of errors, or returns nil if the list of errors is empty. This
|
||||
// function is useful at the end of accumulation to make sure that the value
|
||||
// returned represents the existence of errors.
|
||||
func (e *Error) ErrorOrNil() error {
|
||||
if e == nil {
|
||||
return nil
|
||||
}
|
||||
if len(e.Errors) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *Error) GoString() string {
|
||||
return fmt.Sprintf("*%#v", *e)
|
||||
}
|
||||
|
||||
// WrappedErrors returns the list of errors that this Error is wrapping.
|
||||
// It is an implementation of the errwrap.Wrapper interface so that
|
||||
// multierror.Error can be used with that library.
|
||||
//
|
||||
// This method is not safe to be called concurrently and is no different
|
||||
// than accessing the Errors field directly. It is implemented only to
|
||||
// satisfy the errwrap.Wrapper interface.
|
||||
func (e *Error) WrappedErrors() []error {
|
||||
return e.Errors
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
package multierror
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/errwrap"
|
||||
)
|
||||
|
||||
// Prefix is a helper function that will prefix some text
|
||||
// to the given error. If the error is a multierror.Error, then
|
||||
// it will be prefixed to each wrapped error.
|
||||
//
|
||||
// This is useful to use when appending multiple multierrors
|
||||
// together in order to give better scoping.
|
||||
func Prefix(err error, prefix string) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
format := fmt.Sprintf("%s {{err}}", prefix)
|
||||
switch err := err.(type) {
|
||||
case *Error:
|
||||
// Typed nils can reach here, so initialize if we are nil
|
||||
if err == nil {
|
||||
err = new(Error)
|
||||
}
|
||||
|
||||
// Wrap each of the errors
|
||||
for i, e := range err.Errors {
|
||||
err.Errors[i] = errwrap.Wrapf(format, e)
|
||||
}
|
||||
|
||||
return err
|
||||
default:
|
||||
return errwrap.Wrapf(format, err)
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
crypt/crypt
|
|
@ -0,0 +1,13 @@
|
|||
Copyright (c) 2013 Markus Sonderegger
|
||||
|
||||
Permission to use, copy, modify, and distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
@ -0,0 +1,4 @@
|
|||
# crypt
|
||||
|
||||
Package crypt provides password-based encryption and decryption of
|
||||
data streams.
|
|
@ -0,0 +1,165 @@
|
|||
// Package crypt provides password-based encryption and decryption of
|
||||
// data streams.
|
||||
package crypt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/hmac"
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"hash"
|
||||
"io"
|
||||
)
|
||||
|
||||
const (
|
||||
blockSize = aes.BlockSize // AES block size
|
||||
version = 1
|
||||
)
|
||||
|
||||
// Crypter encrypt/decrypts with AES (Rijndael) in cipher block counter
|
||||
// mode (CTR) and authenticate with HMAC-SHA.
|
||||
type Crypter struct {
|
||||
HashFunc func() hash.Hash
|
||||
HashSize int
|
||||
Key Key
|
||||
BufSize int
|
||||
}
|
||||
|
||||
func (c *Crypter) encHeader(salt, iv, hmacKey []byte) []byte {
|
||||
keySize := c.Key.Size()
|
||||
headerSize := 1 + keySize + blockSize + c.HashSize
|
||||
|
||||
b := make([]byte, headerSize)
|
||||
b[0] = version
|
||||
copy(b[1:1+keySize], salt)
|
||||
copy(b[1+keySize:1+keySize+blockSize], iv)
|
||||
|
||||
mac := hmac.New(c.HashFunc, hmacKey)
|
||||
mac.Write(b[:1+keySize+blockSize])
|
||||
copy(b[1+keySize+blockSize:], mac.Sum(nil))
|
||||
return b
|
||||
}
|
||||
|
||||
func (c *Crypter) bufSize() int {
|
||||
if c.BufSize == 0 {
|
||||
return 2 * 1024 * 1024
|
||||
}
|
||||
return c.BufSize
|
||||
}
|
||||
|
||||
func (c *Crypter) decHeader(b []byte) ([]byte, []byte, error) {
|
||||
if b[0] != version {
|
||||
return nil, nil, errors.New("malformed encrypted packet")
|
||||
}
|
||||
|
||||
keySize := c.Key.Size()
|
||||
salt := b[1 : 1+keySize]
|
||||
iv := b[1+keySize : 1+keySize+blockSize]
|
||||
return salt, iv, nil
|
||||
}
|
||||
|
||||
// Encrypt encrypts from src until either EOF is reached on src or an
|
||||
// error occurs. A successful Encrypt returns err == nil, not err == EOF.
|
||||
func (c *Crypter) Encrypt(dst io.Writer, src io.Reader) (err error) {
|
||||
salt := make([]byte, c.Key.Size())
|
||||
if _, err := rand.Read(salt); err != nil {
|
||||
return err
|
||||
}
|
||||
iv := make([]byte, blockSize)
|
||||
if _, err := rand.Read(iv); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
aesKey, hmacKey := c.Key.Derive(salt)
|
||||
header := c.encHeader(salt, iv, hmacKey)
|
||||
if _, err := dst.Write(header); err != nil {
|
||||
return err
|
||||
}
|
||||
mac := hmac.New(c.HashFunc, hmacKey)
|
||||
mac.Write(header)
|
||||
|
||||
block, err := aes.NewCipher(aesKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
stream := cipher.NewCTR(block, iv)
|
||||
|
||||
buf := make([]byte, c.bufSize())
|
||||
n := 0
|
||||
for {
|
||||
n, err = src.Read(buf)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
mac.Write(buf[:n])
|
||||
stream.XORKeyStream(buf[:n], buf[:n])
|
||||
if _, err = dst.Write(buf[:n]); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = dst.Write(mac.Sum(nil)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decrypt decrypts from src until either EOF is reached on src or an
|
||||
// error occurs. A successful Decrypt returns err == nil, not err == EOF.
|
||||
func (c *Crypter) Decrypt(dst io.Writer, src io.Reader) (err error) {
|
||||
keySize := c.Key.Size()
|
||||
headerSize := 1 + keySize + blockSize + c.HashSize
|
||||
|
||||
header := make([]byte, headerSize)
|
||||
if _, err = src.Read(header); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
salt, iv, err := c.decHeader(header)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
aesKey, hmacKey := c.Key.Derive(salt)
|
||||
|
||||
mac := hmac.New(c.HashFunc, hmacKey)
|
||||
mac.Write(header[:1+keySize+blockSize])
|
||||
|
||||
if !bytes.Equal(header[1+keySize+blockSize:], mac.Sum(nil)) {
|
||||
return errors.New("cannot authenticate header")
|
||||
}
|
||||
mac.Write(header[1+keySize+blockSize:])
|
||||
|
||||
block, err := aes.NewCipher(aesKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
stream := cipher.NewCTR(block, iv)
|
||||
buf := make([]byte, c.bufSize()+c.HashSize)
|
||||
n := 0
|
||||
for {
|
||||
n, err = src.Read(buf)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
stream.XORKeyStream(buf[:n-c.HashSize], buf[:n-c.HashSize])
|
||||
mac.Write(buf[:n-c.HashSize])
|
||||
if !bytes.Equal(buf[n-c.HashSize:n], mac.Sum(nil)) {
|
||||
return errors.New("cannot authenticate packet")
|
||||
}
|
||||
if _, err = dst.Write(buf[:n-c.HashSize]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
package crypt
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
"golang.org/x/crypto/scrypt"
|
||||
)
|
||||
|
||||
// Key defines the key derivation function interface.
|
||||
type Key interface {
|
||||
// Derive returns the AES key and HMAC-SHA key, for the given password,
|
||||
// salt combination.
|
||||
Derive(salt []byte) (aesKey, hmacKey []byte)
|
||||
|
||||
// Size returns the key-size. Key-size should either 16, 24, or 32 to
|
||||
// select AES-128, AES-192, or AES-256.
|
||||
Size() int
|
||||
|
||||
// Reset resets/flushes the key.
|
||||
Reset()
|
||||
}
|
||||
|
||||
type pbkdf2Key struct {
|
||||
password []byte
|
||||
size int
|
||||
}
|
||||
|
||||
// NewPbkdf2Key returns the key derivation function PBKDF2 as defined in
|
||||
// RFC 2898.
|
||||
func NewPbkdf2Key(password []byte, size int) Key {
|
||||
return pbkdf2Key{password: password, size: size}
|
||||
}
|
||||
|
||||
func (k pbkdf2Key) Derive(salt []byte) (aesKey, hmacKey []byte) {
|
||||
key := pbkdf2.Key(k.password, salt, 4096, 2*k.size, sha1.New)
|
||||
aesKey = key[:k.size]
|
||||
hmacKey = key[k.size:]
|
||||
return aesKey, hmacKey
|
||||
}
|
||||
|
||||
func (k pbkdf2Key) Size() int { return k.size }
|
||||
|
||||
func (k pbkdf2Key) Reset() {
|
||||
for i := range k.password {
|
||||
k.password[i] = 0
|
||||
}
|
||||
}
|
||||
|
||||
type scryptKey struct {
|
||||
password []byte
|
||||
size int
|
||||
}
|
||||
|
||||
// NewScryptKey returns the scrypt key derivation function as defined in
|
||||
// Colin Percival's paper "Stronger Key Derivation via Sequential
|
||||
// Memory-Hard Functions".
|
||||
func NewScryptKey(password []byte, size int) Key {
|
||||
return scryptKey{password: password, size: size}
|
||||
}
|
||||
|
||||
func (k scryptKey) Derive(salt []byte) (aesKey, hmacKey []byte) {
|
||||
key, err := scrypt.Key(k.password, salt, 16384, 8, 1, 2*k.size)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
aesKey = key[:k.size]
|
||||
hmacKey = key[k.size:]
|
||||
return aesKey, hmacKey
|
||||
}
|
||||
|
||||
func (k scryptKey) Size() int { return k.size }
|
||||
|
||||
func (k scryptKey) Reset() {
|
||||
for i := range k.password {
|
||||
k.password[i] = 0
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
language: go
|
||||
|
||||
go:
|
||||
- 1.0
|
||||
- 1.1
|
||||
- 1.2
|
||||
- 1.3
|
||||
- tip
|
||||
|
||||
script:
|
||||
- go test
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- go: tip
|
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Mitchell Hashimoto
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
|
@ -0,0 +1,30 @@
|
|||
# colorstring [![Build Status](https://travis-ci.org/mitchellh/colorstring.svg)](https://travis-ci.org/mitchellh/colorstring)
|
||||
|
||||
colorstring is a [Go](http://www.golang.org) library for outputting colored
|
||||
strings to a console using a simple inline syntax in your string to specify
|
||||
the color to print as.
|
||||
|
||||
For example, the string `[blue]hello [red]world` would output the text
|
||||
"hello world" in two colors. The API of colorstring allows for easily disabling
|
||||
colors, adding aliases, etc.
|
||||
|
||||
## Installation
|
||||
|
||||
Standard `go get`:
|
||||
|
||||
```
|
||||
$ go get github.com/mitchellh/colorstring
|
||||
```
|
||||
|
||||
## Usage & Example
|
||||
|
||||
For usage and examples see the [Godoc](http://godoc.org/github.com/mitchellh/colorstring).
|
||||
|
||||
Usage is easy enough:
|
||||
|
||||
```go
|
||||
colorstring.Println("[blue]Hello [red]World!")
|
||||
```
|
||||
|
||||
Additionally, the `Colorize` struct can be used to set options such as
|
||||
custom colors, color disabling, etc.
|
|
@ -0,0 +1,244 @@
|
|||
// colorstring provides functions for colorizing strings for terminal
|
||||
// output.
|
||||
package colorstring
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Color colorizes your strings using the default settings.
|
||||
//
|
||||
// Strings given to Color should use the syntax `[color]` to specify the
|
||||
// color for text following. For example: `[blue]Hello` will return "Hello"
|
||||
// in blue. See DefaultColors for all the supported colors and attributes.
|
||||
//
|
||||
// If an unrecognized color is given, it is ignored and assumed to be part
|
||||
// of the string. For example: `[hi]world` will result in "[hi]world".
|
||||
//
|
||||
// A color reset is appended to the end of every string. This will reset
|
||||
// the color of following strings when you output this text to the same
|
||||
// terminal session.
|
||||
//
|
||||
// If you want to customize any of this behavior, use the Colorize struct.
|
||||
func Color(v string) string {
|
||||
return def.Color(v)
|
||||
}
|
||||
|
||||
// ColorPrefix returns the color sequence that prefixes the given text.
|
||||
//
|
||||
// This is useful when wrapping text if you want to inherit the color
|
||||
// of the wrapped text. For example, "[green]foo" will return "[green]".
|
||||
// If there is no color sequence, then this will return "".
|
||||
func ColorPrefix(v string) string {
|
||||
return def.ColorPrefix(v)
|
||||
}
|
||||
|
||||
// Colorize colorizes your strings, giving you the ability to customize
|
||||
// some of the colorization process.
|
||||
//
|
||||
// The options in Colorize can be set to customize colorization. If you're
|
||||
// only interested in the defaults, just use the top Color function directly,
|
||||
// which creates a default Colorize.
|
||||
type Colorize struct {
|
||||
// Colors maps a color string to the code for that color. The code
|
||||
// is a string so that you can use more complex colors to set foreground,
|
||||
// background, attributes, etc. For example, "boldblue" might be
|
||||
// "1;34"
|
||||
Colors map[string]string
|
||||
|
||||
// If true, color attributes will be ignored. This is useful if you're
|
||||
// outputting to a location that doesn't support colors and you just
|
||||
// want the strings returned.
|
||||
Disable bool
|
||||
|
||||
// Reset, if true, will reset the color after each colorization by
|
||||
// adding a reset code at the end.
|
||||
Reset bool
|
||||
}
|
||||
|
||||
// Color colorizes a string according to the settings setup in the struct.
|
||||
//
|
||||
// For more details on the syntax, see the top-level Color function.
|
||||
func (c *Colorize) Color(v string) string {
|
||||
matches := parseRe.FindAllStringIndex(v, -1)
|
||||
if len(matches) == 0 {
|
||||
return v
|
||||
}
|
||||
|
||||
result := new(bytes.Buffer)
|
||||
colored := false
|
||||
m := []int{0, 0}
|
||||
for _, nm := range matches {
|
||||
// Write the text in between this match and the last
|
||||
result.WriteString(v[m[1]:nm[0]])
|
||||
m = nm
|
||||
|
||||
var replace string
|
||||
if code, ok := c.Colors[v[m[0]+1:m[1]-1]]; ok {
|
||||
colored = true
|
||||
|
||||
if !c.Disable {
|
||||
replace = fmt.Sprintf("\033[%sm", code)
|
||||
}
|
||||
} else {
|
||||
replace = v[m[0]:m[1]]
|
||||
}
|
||||
|
||||
result.WriteString(replace)
|
||||
}
|
||||
result.WriteString(v[m[1]:])
|
||||
|
||||
if colored && c.Reset && !c.Disable {
|
||||
// Write the clear byte at the end
|
||||
result.WriteString("\033[0m")
|
||||
}
|
||||
|
||||
return result.String()
|
||||
}
|
||||
|
||||
// ColorPrefix returns the first color sequence that exists in this string.
|
||||
//
|
||||
// For example: "[green]foo" would return "[green]". If no color sequence
|
||||
// exists, then "" is returned. This is especially useful when wrapping
|
||||
// colored texts to inherit the color of the wrapped text.
|
||||
func (c *Colorize) ColorPrefix(v string) string {
|
||||
return prefixRe.FindString(strings.TrimSpace(v))
|
||||
}
|
||||
|
||||
// DefaultColors are the default colors used when colorizing.
|
||||
//
|
||||
// If the color is surrounded in underscores, such as "_blue_", then that
|
||||
// color will be used for the background color.
|
||||
var DefaultColors map[string]string
|
||||
|
||||
func init() {
|
||||
DefaultColors = map[string]string{
|
||||
// Default foreground/background colors
|
||||
"default": "39",
|
||||
"_default_": "49",
|
||||
|
||||
// Foreground colors
|
||||
"black": "30",
|
||||
"red": "31",
|
||||
"green": "32",
|
||||
"yellow": "33",
|
||||
"blue": "34",
|
||||
"magenta": "35",
|
||||
"cyan": "36",
|
||||
"light_gray": "37",
|
||||
"dark_gray": "90",
|
||||
"light_red": "91",
|
||||
"light_green": "92",
|
||||
"light_yellow": "93",
|
||||
"light_blue": "94",
|
||||
"light_magenta": "95",
|
||||
"light_cyan": "96",
|
||||
"white": "97",
|
||||
|
||||
// Background colors
|
||||
"_black_": "40",
|
||||
"_red_": "41",
|
||||
"_green_": "42",
|
||||
"_yellow_": "43",
|
||||
"_blue_": "44",
|
||||
"_magenta_": "45",
|
||||
"_cyan_": "46",
|
||||
"_light_gray_": "47",
|
||||
"_dark_gray_": "100",
|
||||
"_light_red_": "101",
|
||||
"_light_green_": "102",
|
||||
"_light_yellow_": "103",
|
||||
"_light_blue_": "104",
|
||||
"_light_magenta_": "105",
|
||||
"_light_cyan_": "106",
|
||||
"_white_": "107",
|
||||
|
||||
// Attributes
|
||||
"bold": "1",
|
||||
"dim": "2",
|
||||
"underline": "4",
|
||||
"blink_slow": "5",
|
||||
"blink_fast": "6",
|
||||
"invert": "7",
|
||||
"hidden": "8",
|
||||
|
||||
// Reset to reset everything to their defaults
|
||||
"reset": "0",
|
||||
"reset_bold": "21",
|
||||
}
|
||||
|
||||
def = Colorize{
|
||||
Colors: DefaultColors,
|
||||
Reset: true,
|
||||
}
|
||||
}
|
||||
|
||||
var def Colorize
|
||||
var parseReRaw = `\[[a-z0-9_-]+\]`
|
||||
var parseRe = regexp.MustCompile(`(?i)` + parseReRaw)
|
||||
var prefixRe = regexp.MustCompile(`^(?i)(` + parseReRaw + `)+`)
|
||||
|
||||
// Print is a convenience wrapper for fmt.Print with support for color codes.
|
||||
//
|
||||
// Print formats using the default formats for its operands and writes to
|
||||
// standard output with support for color codes. Spaces are added between
|
||||
// operands when neither is a string. It returns the number of bytes written
|
||||
// and any write error encountered.
|
||||
func Print(a string) (n int, err error) {
|
||||
return fmt.Print(Color(a))
|
||||
}
|
||||
|
||||
// Println is a convenience wrapper for fmt.Println with support for color
|
||||
// codes.
|
||||
//
|
||||
// Println formats using the default formats for its operands and writes to
|
||||
// standard output with support for color codes. Spaces are always added
|
||||
// between operands and a newline is appended. It returns the number of bytes
|
||||
// written and any write error encountered.
|
||||
func Println(a string) (n int, err error) {
|
||||
return fmt.Println(Color(a))
|
||||
}
|
||||
|
||||
// Printf is a convenience wrapper for fmt.Printf with support for color codes.
|
||||
//
|
||||
// Printf formats according to a format specifier and writes to standard output
|
||||
// with support for color codes. It returns the number of bytes written and any
|
||||
// write error encountered.
|
||||
func Printf(format string, a ...interface{}) (n int, err error) {
|
||||
return fmt.Printf(Color(format), a...)
|
||||
}
|
||||
|
||||
// Fprint is a convenience wrapper for fmt.Fprint with support for color codes.
|
||||
//
|
||||
// Fprint formats using the default formats for its operands and writes to w
|
||||
// with support for color codes. Spaces are added between operands when neither
|
||||
// is a string. It returns the number of bytes written and any write error
|
||||
// encountered.
|
||||
func Fprint(w io.Writer, a string) (n int, err error) {
|
||||
return fmt.Fprint(w, Color(a))
|
||||
}
|
||||
|
||||
// Fprintln is a convenience wrapper for fmt.Fprintln with support for color
|
||||
// codes.
|
||||
//
|
||||
// Fprintln formats using the default formats for its operands and writes to w
|
||||
// with support for color codes. Spaces are always added between operands and a
|
||||
// newline is appended. It returns the number of bytes written and any write
|
||||
// error encountered.
|
||||
func Fprintln(w io.Writer, a string) (n int, err error) {
|
||||
return fmt.Fprintln(w, Color(a))
|
||||
}
|
||||
|
||||
// Fprintf is a convenience wrapper for fmt.Fprintf with support for color
|
||||
// codes.
|
||||
//
|
||||
// Fprintf formats according to a format specifier and writes to w with support
|
||||
// for color codes. It returns the number of bytes written and any write error
|
||||
// encountered.
|
||||
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
|
||||
return fmt.Fprintf(w, Color(format), a...)
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2017 Denis Subbotin
|
||||
Copyright (c) 2017 Nika Jones
|
||||
Copyright (c) 2017 Philip Schlump
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,31 @@
|
|||
package base58
|
||||
|
||||
// Alphabet is a a b58 alphabet.
|
||||
type Alphabet struct {
|
||||
decode [128]int8
|
||||
encode [58]byte
|
||||
}
|
||||
|
||||
// NewAlphabet creates a new alphabet from the passed string.
|
||||
//
|
||||
// It panics if the passed string is not 58 bytes long or isn't valid ASCII.
|
||||
func NewAlphabet(s string) *Alphabet {
|
||||
if len(s) != 58 {
|
||||
panic("base58 alphabets must be 58 bytes long")
|
||||
}
|
||||
ret := new(Alphabet)
|
||||
copy(ret.encode[:], s)
|
||||
for i := range ret.decode {
|
||||
ret.decode[i] = -1
|
||||
}
|
||||
for i, b := range ret.encode {
|
||||
ret.decode[b] = int8(i)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// BTCAlphabet is the bitcoin base58 alphabet.
|
||||
var BTCAlphabet = NewAlphabet("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz")
|
||||
|
||||
// FlickrAlphabet is the flickr base58 alphabet.
|
||||
var FlickrAlphabet = NewAlphabet("123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ")
|
|
@ -0,0 +1,255 @@
|
|||
package base58
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
var (
|
||||
bn0 = big.NewInt(0)
|
||||
bn58 = big.NewInt(58)
|
||||
)
|
||||
|
||||
// Encode encodes the passed bytes into a base58 encoded string.
|
||||
func Encode(bin []byte) string {
|
||||
return FastBase58Encoding(bin)
|
||||
}
|
||||
|
||||
// EncodeAlphabet encodes the passed bytes into a base58 encoded string with the
|
||||
// passed alphabet.
|
||||
func EncodeAlphabet(bin []byte, alphabet *Alphabet) string {
|
||||
return FastBase58EncodingAlphabet(bin, alphabet)
|
||||
}
|
||||
|
||||
// FastBase58Encoding encodes the passed bytes into a base58 encoded string.
|
||||
func FastBase58Encoding(bin []byte) string {
|
||||
return FastBase58EncodingAlphabet(bin, BTCAlphabet)
|
||||
}
|
||||
|
||||
// FastBase58EncodingAlphabet encodes the passed bytes into a base58 encoded
|
||||
// string with the passed alphabet.
|
||||
func FastBase58EncodingAlphabet(bin []byte, alphabet *Alphabet) string {
|
||||
zero := alphabet.encode[0]
|
||||
|
||||
binsz := len(bin)
|
||||
var i, j, zcount, high int
|
||||
var carry uint32
|
||||
|
||||
for zcount < binsz && bin[zcount] == 0 {
|
||||
zcount++
|
||||
}
|
||||
|
||||
size := (binsz-zcount)*138/100 + 1
|
||||
var buf = make([]byte, size)
|
||||
|
||||
high = size - 1
|
||||
for i = zcount; i < binsz; i++ {
|
||||
j = size - 1
|
||||
for carry = uint32(bin[i]); j > high || carry != 0; j-- {
|
||||
carry = carry + 256*uint32(buf[j])
|
||||
buf[j] = byte(carry % 58)
|
||||
carry /= 58
|
||||
}
|
||||
high = j
|
||||
}
|
||||
|
||||
for j = 0; j < size && buf[j] == 0; j++ {
|
||||
}
|
||||
|
||||
var b58 = make([]byte, size-j+zcount)
|
||||
|
||||
if zcount != 0 {
|
||||
for i = 0; i < zcount; i++ {
|
||||
b58[i] = zero
|
||||
}
|
||||
}
|
||||
|
||||
for i = zcount; j < size; i++ {
|
||||
b58[i] = alphabet.encode[buf[j]]
|
||||
j++
|
||||
}
|
||||
|
||||
return string(b58)
|
||||
}
|
||||
|
||||
// TrivialBase58Encoding encodes the passed bytes into a base58 encoded string
|
||||
// (inefficiently).
|
||||
func TrivialBase58Encoding(a []byte) string {
|
||||
return TrivialBase58EncodingAlphabet(a, BTCAlphabet)
|
||||
}
|
||||
|
||||
// TrivialBase58EncodingAlphabet encodes the passed bytes into a base58 encoded
|
||||
// string (inefficiently) with the passed alphabet.
|
||||
func TrivialBase58EncodingAlphabet(a []byte, alphabet *Alphabet) string {
|
||||
zero := alphabet.encode[0]
|
||||
idx := len(a)*138/100 + 1
|
||||
buf := make([]byte, idx)
|
||||
bn := new(big.Int).SetBytes(a)
|
||||
var mo *big.Int
|
||||
for bn.Cmp(bn0) != 0 {
|
||||
bn, mo = bn.DivMod(bn, bn58, new(big.Int))
|
||||
idx--
|
||||
buf[idx] = alphabet.encode[mo.Int64()]
|
||||
}
|
||||
for i := range a {
|
||||
if a[i] != 0 {
|
||||
break
|
||||
}
|
||||
idx--
|
||||
buf[idx] = zero
|
||||
}
|
||||
return string(buf[idx:])
|
||||
}
|
||||
|
||||
// Decode decodes the base58 encoded bytes.
|
||||
func Decode(str string) ([]byte, error) {
|
||||
return FastBase58Decoding(str)
|
||||
}
|
||||
|
||||
// DecodeAlphabet decodes the base58 encoded bytes using the given b58 alphabet.
|
||||
func DecodeAlphabet(str string, alphabet *Alphabet) ([]byte, error) {
|
||||
return FastBase58DecodingAlphabet(str, alphabet)
|
||||
}
|
||||
|
||||
// FastBase58Decoding decodes the base58 encoded bytes.
|
||||
func FastBase58Decoding(str string) ([]byte, error) {
|
||||
return FastBase58DecodingAlphabet(str, BTCAlphabet)
|
||||
}
|
||||
|
||||
// FastBase58DecodingAlphabet decodes the base58 encoded bytes using the given
|
||||
// b58 alphabet.
|
||||
func FastBase58DecodingAlphabet(str string, alphabet *Alphabet) ([]byte, error) {
|
||||
if len(str) == 0 {
|
||||
return nil, fmt.Errorf("zero length string")
|
||||
}
|
||||
|
||||
var (
|
||||
t uint64
|
||||
zmask, c uint32
|
||||
zcount int
|
||||
|
||||
b58u = []rune(str)
|
||||
b58sz = len(b58u)
|
||||
|
||||
outisz = (b58sz + 3) / 4 // check to see if we need to change this buffer size to optimize
|
||||
binu = make([]byte, (b58sz+3)*3)
|
||||
bytesleft = b58sz % 4
|
||||
|
||||
zero = rune(alphabet.encode[0])
|
||||
)
|
||||
|
||||
if bytesleft > 0 {
|
||||
zmask = (0xffffffff << uint32(bytesleft*8))
|
||||
} else {
|
||||
bytesleft = 4
|
||||
}
|
||||
|
||||
var outi = make([]uint32, outisz)
|
||||
|
||||
for i := 0; i < b58sz && b58u[i] == zero; i++ {
|
||||
zcount++
|
||||
}
|
||||
|
||||
for _, r := range b58u {
|
||||
if r > 127 {
|
||||
return nil, fmt.Errorf("High-bit set on invalid digit")
|
||||
}
|
||||
if alphabet.decode[r] == -1 {
|
||||
return nil, fmt.Errorf("Invalid base58 digit (%q)", r)
|
||||
}
|
||||
|
||||
c = uint32(alphabet.decode[r])
|
||||
|
||||
for j := (outisz - 1); j >= 0; j-- {
|
||||
t = uint64(outi[j])*58 + uint64(c)
|
||||
c = uint32(t>>32) & 0x3f
|
||||
outi[j] = uint32(t & 0xffffffff)
|
||||
}
|
||||
|
||||
if c > 0 {
|
||||
return nil, fmt.Errorf("Output number too big (carry to the next int32)")
|
||||
}
|
||||
|
||||
if outi[0]&zmask != 0 {
|
||||
return nil, fmt.Errorf("Output number too big (last int32 filled too far)")
|
||||
}
|
||||
}
|
||||
|
||||
// the nested for-loop below is the same as the original code:
|
||||
// switch (bytesleft) {
|
||||
// case 3:
|
||||
// *(binu++) = (outi[0] & 0xff0000) >> 16;
|
||||
// //-fallthrough
|
||||
// case 2:
|
||||
// *(binu++) = (outi[0] & 0xff00) >> 8;
|
||||
// //-fallthrough
|
||||
// case 1:
|
||||
// *(binu++) = (outi[0] & 0xff);
|
||||
// ++j;
|
||||
// //-fallthrough
|
||||
// default:
|
||||
// break;
|
||||
// }
|
||||
//
|
||||
// for (; j < outisz; ++j)
|
||||
// {
|
||||
// *(binu++) = (outi[j] >> 0x18) & 0xff;
|
||||
// *(binu++) = (outi[j] >> 0x10) & 0xff;
|
||||
// *(binu++) = (outi[j] >> 8) & 0xff;
|
||||
// *(binu++) = (outi[j] >> 0) & 0xff;
|
||||
// }
|
||||
var j, cnt int
|
||||
for j, cnt = 0, 0; j < outisz; j++ {
|
||||
for mask := byte(bytesleft-1) * 8; mask <= 0x18; mask, cnt = mask-8, cnt+1 {
|
||||
binu[cnt] = byte(outi[j] >> mask)
|
||||
}
|
||||
if j == 0 {
|
||||
bytesleft = 4 // because it could be less than 4 the first time through
|
||||
}
|
||||
}
|
||||
|
||||
for n, v := range binu {
|
||||
if v > 0 {
|
||||
start := n - zcount
|
||||
if start < 0 {
|
||||
start = 0
|
||||
}
|
||||
return binu[start:cnt], nil
|
||||
}
|
||||
}
|
||||
return binu[:cnt], nil
|
||||
}
|
||||
|
||||
// TrivialBase58Decoding decodes the base58 encoded bytes (inefficiently).
|
||||
func TrivialBase58Decoding(str string) ([]byte, error) {
|
||||
return TrivialBase58DecodingAlphabet(str, BTCAlphabet)
|
||||
}
|
||||
|
||||
// TrivialBase58DecodingAlphabet decodes the base58 encoded bytes
|
||||
// (inefficiently) using the given b58 alphabet.
|
||||
func TrivialBase58DecodingAlphabet(str string, alphabet *Alphabet) ([]byte, error) {
|
||||
zero := alphabet.encode[0]
|
||||
|
||||
var zcount int
|
||||
for i := 0; i < len(str) && str[i] == zero; i++ {
|
||||
zcount++
|
||||
}
|
||||
leading := make([]byte, zcount)
|
||||
|
||||
var padChar rune = -1
|
||||
src := []byte(str)
|
||||
j := 0
|
||||
for ; j < len(src) && src[j] == byte(padChar); j++ {
|
||||
}
|
||||
|
||||
n := new(big.Int)
|
||||
for i := range src[j:] {
|
||||
c := alphabet.decode[src[i]]
|
||||
if c == -1 {
|
||||
return nil, fmt.Errorf("illegal base58 data at input index: %d", i)
|
||||
}
|
||||
n.Mul(n, bn58)
|
||||
n.Add(n, big.NewInt(int64(c)))
|
||||
}
|
||||
return append(leading, n.Bytes()...), nil
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
|
@ -0,0 +1,11 @@
|
|||
language: go
|
||||
go_import_path: github.com/pkg/errors
|
||||
go:
|
||||
- 1.4.3
|
||||
- 1.5.4
|
||||
- 1.6.2
|
||||
- 1.7.1
|
||||
- tip
|
||||
|
||||
script:
|
||||
- go test -v ./...
|
|
@ -0,0 +1,23 @@
|
|||
Copyright (c) 2015, Dave Cheney <dave@cheney.net>
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,52 @@
|
|||
# errors [![Travis-CI](https://travis-ci.org/pkg/errors.svg)](https://travis-ci.org/pkg/errors) [![AppVeyor](https://ci.appveyor.com/api/projects/status/b98mptawhudj53ep/branch/master?svg=true)](https://ci.appveyor.com/project/davecheney/errors/branch/master) [![GoDoc](https://godoc.org/github.com/pkg/errors?status.svg)](http://godoc.org/github.com/pkg/errors) [![Report card](https://goreportcard.com/badge/github.com/pkg/errors)](https://goreportcard.com/report/github.com/pkg/errors)
|
||||
|
||||
Package errors provides simple error handling primitives.
|
||||
|
||||
`go get github.com/pkg/errors`
|
||||
|
||||
The traditional error handling idiom in Go is roughly akin to
|
||||
```go
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
```
|
||||
which applied recursively up the call stack results in error reports without context or debugging information. The errors package allows programmers to add context to the failure path in their code in a way that does not destroy the original value of the error.
|
||||
|
||||
## Adding context to an error
|
||||
|
||||
The errors.Wrap function returns a new error that adds context to the original error. For example
|
||||
```go
|
||||
_, err := ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "read failed")
|
||||
}
|
||||
```
|
||||
## Retrieving the cause of an error
|
||||
|
||||
Using `errors.Wrap` constructs a stack of errors, adding context to the preceding error. Depending on the nature of the error it may be necessary to reverse the operation of errors.Wrap to retrieve the original error for inspection. Any error value which implements this interface can be inspected by `errors.Cause`.
|
||||
```go
|
||||
type causer interface {
|
||||
Cause() error
|
||||
}
|
||||
```
|
||||
`errors.Cause` will recursively retrieve the topmost error which does not implement `causer`, which is assumed to be the original cause. For example:
|
||||
```go
|
||||
switch err := errors.Cause(err).(type) {
|
||||
case *MyError:
|
||||
// handle specifically
|
||||
default:
|
||||
// unknown error
|
||||
}
|
||||
```
|
||||
|
||||
[Read the package documentation for more information](https://godoc.org/github.com/pkg/errors).
|
||||
|
||||
## Contributing
|
||||
|
||||
We welcome pull requests, bug fixes and issue reports. With that said, the bar for adding new symbols to this package is intentionally set high.
|
||||
|
||||
Before proposing a change, please discuss your change by raising an issue.
|
||||
|
||||
## Licence
|
||||
|
||||
BSD-2-Clause
|
|
@ -0,0 +1,32 @@
|
|||
version: build-{build}.{branch}
|
||||
|
||||
clone_folder: C:\gopath\src\github.com\pkg\errors
|
||||
shallow_clone: true # for startup speed
|
||||
|
||||
environment:
|
||||
GOPATH: C:\gopath
|
||||
|
||||
platform:
|
||||
- x64
|
||||
|
||||
# http://www.appveyor.com/docs/installed-software
|
||||
install:
|
||||
# some helpful output for debugging builds
|
||||
- go version
|
||||
- go env
|
||||
# pre-installed MinGW at C:\MinGW is 32bit only
|
||||
# but MSYS2 at C:\msys64 has mingw64
|
||||
- set PATH=C:\msys64\mingw64\bin;%PATH%
|
||||
- gcc --version
|
||||
- g++ --version
|
||||
|
||||
build_script:
|
||||
- go install -v ./...
|
||||
|
||||
test_script:
|
||||
- set PATH=C:\gopath\bin;%PATH%
|
||||
- go test -v ./...
|
||||
|
||||
#artifacts:
|
||||
# - path: '%GOPATH%\bin\*.exe'
|
||||
deploy: off
|
|
@ -0,0 +1,269 @@
|
|||
// Package errors provides simple error handling primitives.
|
||||
//
|
||||
// The traditional error handling idiom in Go is roughly akin to
|
||||
//
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// which applied recursively up the call stack results in error reports
|
||||
// without context or debugging information. The errors package allows
|
||||
// programmers to add context to the failure path in their code in a way
|
||||
// that does not destroy the original value of the error.
|
||||
//
|
||||
// Adding context to an error
|
||||
//
|
||||
// The errors.Wrap function returns a new error that adds context to the
|
||||
// original error by recording a stack trace at the point Wrap is called,
|
||||
// and the supplied message. For example
|
||||
//
|
||||
// _, err := ioutil.ReadAll(r)
|
||||
// if err != nil {
|
||||
// return errors.Wrap(err, "read failed")
|
||||
// }
|
||||
//
|
||||
// If additional control is required the errors.WithStack and errors.WithMessage
|
||||
// functions destructure errors.Wrap into its component operations of annotating
|
||||
// an error with a stack trace and an a message, respectively.
|
||||
//
|
||||
// Retrieving the cause of an error
|
||||
//
|
||||
// Using errors.Wrap constructs a stack of errors, adding context to the
|
||||
// preceding error. Depending on the nature of the error it may be necessary
|
||||
// to reverse the operation of errors.Wrap to retrieve the original error
|
||||
// for inspection. Any error value which implements this interface
|
||||
//
|
||||
// type causer interface {
|
||||
// Cause() error
|
||||
// }
|
||||
//
|
||||
// can be inspected by errors.Cause. errors.Cause will recursively retrieve
|
||||
// the topmost error which does not implement causer, which is assumed to be
|
||||
// the original cause. For example:
|
||||
//
|
||||
// switch err := errors.Cause(err).(type) {
|
||||
// case *MyError:
|
||||
// // handle specifically
|
||||
// default:
|
||||
// // unknown error
|
||||
// }
|
||||
//
|
||||
// causer interface is not exported by this package, but is considered a part
|
||||
// of stable public API.
|
||||
//
|
||||
// Formatted printing of errors
|
||||
//
|
||||
// All error values returned from this package implement fmt.Formatter and can
|
||||
// be formatted by the fmt package. The following verbs are supported
|
||||
//
|
||||
// %s print the error. If the error has a Cause it will be
|
||||
// printed recursively
|
||||
// %v see %s
|
||||
// %+v extended format. Each Frame of the error's StackTrace will
|
||||
// be printed in detail.
|
||||
//
|
||||
// Retrieving the stack trace of an error or wrapper
|
||||
//
|
||||
// New, Errorf, Wrap, and Wrapf record a stack trace at the point they are
|
||||
// invoked. This information can be retrieved with the following interface.
|
||||
//
|
||||
// type stackTracer interface {
|
||||
// StackTrace() errors.StackTrace
|
||||
// }
|
||||
//
|
||||
// Where errors.StackTrace is defined as
|
||||
//
|
||||
// type StackTrace []Frame
|
||||
//
|
||||
// The Frame type represents a call site in the stack trace. Frame supports
|
||||
// the fmt.Formatter interface that can be used for printing information about
|
||||
// the stack trace of this error. For example:
|
||||
//
|
||||
// if err, ok := err.(stackTracer); ok {
|
||||
// for _, f := range err.StackTrace() {
|
||||
// fmt.Printf("%+s:%d", f)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// stackTracer interface is not exported by this package, but is considered a part
|
||||
// of stable public API.
|
||||
//
|
||||
// See the documentation for Frame.Format for more details.
|
||||
package errors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// New returns an error with the supplied message.
|
||||
// New also records the stack trace at the point it was called.
|
||||
func New(message string) error {
|
||||
return &fundamental{
|
||||
msg: message,
|
||||
stack: callers(),
|
||||
}
|
||||
}
|
||||
|
||||
// Errorf formats according to a format specifier and returns the string
|
||||
// as a value that satisfies error.
|
||||
// Errorf also records the stack trace at the point it was called.
|
||||
func Errorf(format string, args ...interface{}) error {
|
||||
return &fundamental{
|
||||
msg: fmt.Sprintf(format, args...),
|
||||
stack: callers(),
|
||||
}
|
||||
}
|
||||
|
||||
// fundamental is an error that has a message and a stack, but no caller.
|
||||
type fundamental struct {
|
||||
msg string
|
||||
*stack
|
||||
}
|
||||
|
||||
func (f *fundamental) Error() string { return f.msg }
|
||||
|
||||
func (f *fundamental) Format(s fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 'v':
|
||||
if s.Flag('+') {
|
||||
io.WriteString(s, f.msg)
|
||||
f.stack.Format(s, verb)
|
||||
return
|
||||
}
|
||||
fallthrough
|
||||
case 's':
|
||||
io.WriteString(s, f.msg)
|
||||
case 'q':
|
||||
fmt.Fprintf(s, "%q", f.msg)
|
||||
}
|
||||
}
|
||||
|
||||
// WithStack annotates err with a stack trace at the point WithStack was called.
|
||||
// If err is nil, WithStack returns nil.
|
||||
func WithStack(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
return &withStack{
|
||||
err,
|
||||
callers(),
|
||||
}
|
||||
}
|
||||
|
||||
type withStack struct {
|
||||
error
|
||||
*stack
|
||||
}
|
||||
|
||||
func (w *withStack) Cause() error { return w.error }
|
||||
|
||||
func (w *withStack) Format(s fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 'v':
|
||||
if s.Flag('+') {
|
||||
fmt.Fprintf(s, "%+v", w.Cause())
|
||||
w.stack.Format(s, verb)
|
||||
return
|
||||
}
|
||||
fallthrough
|
||||
case 's':
|
||||
io.WriteString(s, w.Error())
|
||||
case 'q':
|
||||
fmt.Fprintf(s, "%q", w.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// Wrap returns an error annotating err with a stack trace
|
||||
// at the point Wrap is called, and the supplied message.
|
||||
// If err is nil, Wrap returns nil.
|
||||
func Wrap(err error, message string) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
err = &withMessage{
|
||||
cause: err,
|
||||
msg: message,
|
||||
}
|
||||
return &withStack{
|
||||
err,
|
||||
callers(),
|
||||
}
|
||||
}
|
||||
|
||||
// Wrapf returns an error annotating err with a stack trace
|
||||
// at the point Wrapf is call, and the format specifier.
|
||||
// If err is nil, Wrapf returns nil.
|
||||
func Wrapf(err error, format string, args ...interface{}) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
err = &withMessage{
|
||||
cause: err,
|
||||
msg: fmt.Sprintf(format, args...),
|
||||
}
|
||||
return &withStack{
|
||||
err,
|
||||
callers(),
|
||||
}
|
||||
}
|
||||
|
||||
// WithMessage annotates err with a new message.
|
||||
// If err is nil, WithMessage returns nil.
|
||||
func WithMessage(err error, message string) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
return &withMessage{
|
||||
cause: err,
|
||||
msg: message,
|
||||
}
|
||||
}
|
||||
|
||||
type withMessage struct {
|
||||
cause error
|
||||
msg string
|
||||
}
|
||||
|
||||
func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() }
|
||||
func (w *withMessage) Cause() error { return w.cause }
|
||||
|
||||
func (w *withMessage) Format(s fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 'v':
|
||||
if s.Flag('+') {
|
||||
fmt.Fprintf(s, "%+v\n", w.Cause())
|
||||
io.WriteString(s, w.msg)
|
||||
return
|
||||
}
|
||||
fallthrough
|
||||
case 's', 'q':
|
||||
io.WriteString(s, w.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// Cause returns the underlying cause of the error, if possible.
|
||||
// An error value has a cause if it implements the following
|
||||
// interface:
|
||||
//
|
||||
// type causer interface {
|
||||
// Cause() error
|
||||
// }
|
||||
//
|
||||
// If the error does not implement Cause, the original error will
|
||||
// be returned. If the error is nil, nil will be returned without further
|
||||
// investigation.
|
||||
func Cause(err error) error {
|
||||
type causer interface {
|
||||
Cause() error
|
||||
}
|
||||
|
||||
for err != nil {
|
||||
cause, ok := err.(causer)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
err = cause.Cause()
|
||||
}
|
||||
return err
|
||||
}
|
|
@ -0,0 +1,178 @@
|
|||
package errors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"path"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Frame represents a program counter inside a stack frame.
|
||||
type Frame uintptr
|
||||
|
||||
// pc returns the program counter for this frame;
|
||||
// multiple frames may have the same PC value.
|
||||
func (f Frame) pc() uintptr { return uintptr(f) - 1 }
|
||||
|
||||
// file returns the full path to the file that contains the
|
||||
// function for this Frame's pc.
|
||||
func (f Frame) file() string {
|
||||
fn := runtime.FuncForPC(f.pc())
|
||||
if fn == nil {
|
||||
return "unknown"
|
||||
}
|
||||
file, _ := fn.FileLine(f.pc())
|
||||
return file
|
||||
}
|
||||
|
||||
// line returns the line number of source code of the
|
||||
// function for this Frame's pc.
|
||||
func (f Frame) line() int {
|
||||
fn := runtime.FuncForPC(f.pc())
|
||||
if fn == nil {
|
||||
return 0
|
||||
}
|
||||
_, line := fn.FileLine(f.pc())
|
||||
return line
|
||||
}
|
||||
|
||||
// Format formats the frame according to the fmt.Formatter interface.
|
||||
//
|
||||
// %s source file
|
||||
// %d source line
|
||||
// %n function name
|
||||
// %v equivalent to %s:%d
|
||||
//
|
||||
// Format accepts flags that alter the printing of some verbs, as follows:
|
||||
//
|
||||
// %+s path of source file relative to the compile time GOPATH
|
||||
// %+v equivalent to %+s:%d
|
||||
func (f Frame) Format(s fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 's':
|
||||
switch {
|
||||
case s.Flag('+'):
|
||||
pc := f.pc()
|
||||
fn := runtime.FuncForPC(pc)
|
||||
if fn == nil {
|
||||
io.WriteString(s, "unknown")
|
||||
} else {
|
||||
file, _ := fn.FileLine(pc)
|
||||
fmt.Fprintf(s, "%s\n\t%s", fn.Name(), file)
|
||||
}
|
||||
default:
|
||||
io.WriteString(s, path.Base(f.file()))
|
||||
}
|
||||
case 'd':
|
||||
fmt.Fprintf(s, "%d", f.line())
|
||||
case 'n':
|
||||
name := runtime.FuncForPC(f.pc()).Name()
|
||||
io.WriteString(s, funcname(name))
|
||||
case 'v':
|
||||
f.Format(s, 's')
|
||||
io.WriteString(s, ":")
|
||||
f.Format(s, 'd')
|
||||
}
|
||||
}
|
||||
|
||||
// StackTrace is stack of Frames from innermost (newest) to outermost (oldest).
|
||||
type StackTrace []Frame
|
||||
|
||||
func (st StackTrace) Format(s fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 'v':
|
||||
switch {
|
||||
case s.Flag('+'):
|
||||
for _, f := range st {
|
||||
fmt.Fprintf(s, "\n%+v", f)
|
||||
}
|
||||
case s.Flag('#'):
|
||||
fmt.Fprintf(s, "%#v", []Frame(st))
|
||||
default:
|
||||
fmt.Fprintf(s, "%v", []Frame(st))
|
||||
}
|
||||
case 's':
|
||||
fmt.Fprintf(s, "%s", []Frame(st))
|
||||
}
|
||||
}
|
||||
|
||||
// stack represents a stack of program counters.
|
||||
type stack []uintptr
|
||||
|
||||
func (s *stack) Format(st fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 'v':
|
||||
switch {
|
||||
case st.Flag('+'):
|
||||
for _, pc := range *s {
|
||||
f := Frame(pc)
|
||||
fmt.Fprintf(st, "\n%+v", f)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *stack) StackTrace() StackTrace {
|
||||
f := make([]Frame, len(*s))
|
||||
for i := 0; i < len(f); i++ {
|
||||
f[i] = Frame((*s)[i])
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
func callers() *stack {
|
||||
const depth = 32
|
||||
var pcs [depth]uintptr
|
||||
n := runtime.Callers(3, pcs[:])
|
||||
var st stack = pcs[0:n]
|
||||
return &st
|
||||
}
|
||||
|
||||
// funcname removes the path prefix component of a function's name reported by func.Name().
|
||||
func funcname(name string) string {
|
||||
i := strings.LastIndex(name, "/")
|
||||
name = name[i+1:]
|
||||
i = strings.Index(name, ".")
|
||||
return name[i+1:]
|
||||
}
|
||||
|
||||
func trimGOPATH(name, file string) string {
|
||||
// Here we want to get the source file path relative to the compile time
|
||||
// GOPATH. As of Go 1.6.x there is no direct way to know the compiled
|
||||
// GOPATH at runtime, but we can infer the number of path segments in the
|
||||
// GOPATH. We note that fn.Name() returns the function name qualified by
|
||||
// the import path, which does not include the GOPATH. Thus we can trim
|
||||
// segments from the beginning of the file path until the number of path
|
||||
// separators remaining is one more than the number of path separators in
|
||||
// the function name. For example, given:
|
||||
//
|
||||
// GOPATH /home/user
|
||||
// file /home/user/src/pkg/sub/file.go
|
||||
// fn.Name() pkg/sub.Type.Method
|
||||
//
|
||||
// We want to produce:
|
||||
//
|
||||
// pkg/sub/file.go
|
||||
//
|
||||
// From this we can easily see that fn.Name() has one less path separator
|
||||
// than our desired output. We count separators from the end of the file
|
||||
// path until it finds two more than in the function name and then move
|
||||
// one character forward to preserve the initial path segment without a
|
||||
// leading separator.
|
||||
const sep = "/"
|
||||
goal := strings.Count(name, sep) + 2
|
||||
i := len(file)
|
||||
for n := 0; n < goal; n++ {
|
||||
i = strings.LastIndex(file[:i], sep)
|
||||
if i == -1 {
|
||||
// not enough separators found, set i so that the slice expression
|
||||
// below leaves file unmodified
|
||||
i = -len(sep)
|
||||
break
|
||||
}
|
||||
}
|
||||
// get back to 0 or trim the leading separator
|
||||
file = file[i+len(sep):]
|
||||
return file
|
||||
}
|
|
@ -0,0 +1,191 @@
|
|||
package keypair
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
crypto_rand "crypto/rand"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"hash/fnv"
|
||||
"io"
|
||||
math_rand "math/rand"
|
||||
|
||||
base65536 "github.com/Nightbug/go-base65536"
|
||||
"github.com/mr-tron/base58/base58"
|
||||
"golang.org/x/crypto/nacl/box"
|
||||
)
|
||||
|
||||
var baseType = "base58"
|
||||
|
||||
func SetBase(s string) (err error) {
|
||||
if s == "base58" {
|
||||
baseType = s
|
||||
} else if s == "base65536" {
|
||||
baseType = s
|
||||
} else {
|
||||
err = errors.New("no such base")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type KeyPair struct {
|
||||
Public string `json:"public"`
|
||||
Private string `json:"private,omitempty"`
|
||||
private *[32]byte
|
||||
public *[32]byte
|
||||
}
|
||||
|
||||
func (kp KeyPair) String() string {
|
||||
b, _ := json.Marshal(kp)
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// New will generate a new key pair, or reload a keypair
|
||||
// from a public key or a public-private key pair.
|
||||
func New(kpLoad ...KeyPair) (kp KeyPair, err error) {
|
||||
kp = KeyPair{}
|
||||
if len(kpLoad) > 0 {
|
||||
kp.Public = kpLoad[0].Public
|
||||
kp.Private = kpLoad[0].Private
|
||||
} else {
|
||||
kp.Public, kp.Private, err = generateKeyPair()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
kp.public, err = keyToBytes(kp.Public)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if len(kp.Private) > 0 {
|
||||
kp.private, err = keyToBytes(kp.Private)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func generateKeyPair() (publicKey, privateKey string, err error) {
|
||||
publicKeyBytes, privateKeyBytes, err := box.GenerateKey(crypto_rand.Reader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if baseType == "base58" {
|
||||
publicKey = base58.FastBase58Encoding(publicKeyBytes[:])
|
||||
privateKey = base58.FastBase58Encoding(privateKeyBytes[:])
|
||||
} else if baseType == "base65536" {
|
||||
publicKey = base65536.Marshal(publicKeyBytes[:])
|
||||
privateKey = base65536.Marshal(privateKeyBytes[:])
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func generateDeterministicKey(seedBytes []byte) (publicKey, privateKey string) {
|
||||
h := fnv.New32a()
|
||||
h.Write(seedBytes)
|
||||
math_rand.Seed(int64(h.Sum32()))
|
||||
b := make([]byte, 512)
|
||||
math_rand.Read(b)
|
||||
reader := bytes.NewReader(b)
|
||||
publicKeyBytes, privateKeyBytes, err := box.GenerateKey(reader)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if baseType == "base58" {
|
||||
publicKey = base58.FastBase58Encoding(publicKeyBytes[:])
|
||||
privateKey = base58.FastBase58Encoding(privateKeyBytes[:])
|
||||
} else if baseType == "base65536" {
|
||||
publicKey = base65536.Marshal(publicKeyBytes[:])
|
||||
privateKey = base65536.Marshal(privateKeyBytes[:])
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// NewDeterministic will return a deterministic key
|
||||
func NewDeterministic(passphrase string) (kp KeyPair, err error) {
|
||||
pub, priv := generateDeterministicKey([]byte(passphrase))
|
||||
kp, err = New(KeyPair{Public: pub, Private: priv})
|
||||
return
|
||||
}
|
||||
|
||||
func keyToBytes(s string) (key *[32]byte, err error) {
|
||||
var keyBytes []byte
|
||||
if baseType == "base58" {
|
||||
keyBytes, err = base58.FastBase58Decoding(s)
|
||||
} else if baseType == "base65536" {
|
||||
err = base65536.Unmarshal([]byte(s), &keyBytes)
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
key = new([32]byte)
|
||||
copy(key[:], keyBytes[:32])
|
||||
return
|
||||
}
|
||||
|
||||
// Encrypt a message for a recipient
|
||||
func (kp KeyPair) Encrypt(msg []byte, recipientPublicKey string) (encrypted []byte, err error) {
|
||||
recipient, err := New(KeyPair{Public: recipientPublicKey})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
encrypted, err = encryptWithKeyPair(msg, kp.private, recipient.public)
|
||||
return
|
||||
}
|
||||
|
||||
// Decrypt a message
|
||||
func (kp KeyPair) Decrypt(encrypted []byte, senderPublicKey string) (msg []byte, err error) {
|
||||
sender, err := New(KeyPair{Public: senderPublicKey})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
msg, err = decryptWithKeyPair(encrypted, sender.public, kp.private)
|
||||
return
|
||||
}
|
||||
|
||||
func encryptWithKeyPair(msg []byte, senderPrivateKey, recipientPublicKey *[32]byte) (encrypted []byte, err error) {
|
||||
// You must use a different nonce for each message you encrypt with the
|
||||
// same key. Since the nonce here is 192 bits long, a random value
|
||||
// provides a sufficiently small probability of repeats.
|
||||
var nonce [24]byte
|
||||
if _, err = io.ReadFull(crypto_rand.Reader, nonce[:]); err != nil {
|
||||
return
|
||||
}
|
||||
// This encrypts msg and appends the result to the nonce.
|
||||
encrypted = box.Seal(nonce[:], msg, &nonce, recipientPublicKey, senderPrivateKey)
|
||||
return
|
||||
}
|
||||
|
||||
func decryptWithKeyPair(enc []byte, senderPublicKey, recipientPrivateKey *[32]byte) (decrypted []byte, err error) {
|
||||
// The recipient can decrypt the message using their private key and the
|
||||
// sender's public key. When you decrypt, you must use the same nonce you
|
||||
// used to encrypt the message. One way to achieve this is to store the
|
||||
// nonce alongside the encrypted message. Above, we stored the nonce in the
|
||||
// first 24 bytes of the encrypted text.
|
||||
var decryptNonce [24]byte
|
||||
copy(decryptNonce[:], enc[:24])
|
||||
var ok bool
|
||||
decrypted, ok = box.Open(nil, enc[24:], &decryptNonce, senderPublicKey, recipientPrivateKey)
|
||||
if !ok {
|
||||
err = errors.New("keypair decryption failed")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// sliceForAppend takes a slice and a requested number of bytes. It returns a
|
||||
// slice with the contents of the given slice followed by that many bytes and a
|
||||
// second slice that aliases into it and contains only the extra bytes. If the
|
||||
// original slice has sufficient capacity then no allocation is performed.
|
||||
func sliceForAppend(in []byte, n int) (head, tail []byte) {
|
||||
if total := len(in) + n; cap(in) >= total {
|
||||
head = in[:total]
|
||||
} else {
|
||||
head = make([]byte, total)
|
||||
copy(head, in)
|
||||
}
|
||||
tail = head[len(in):]
|
||||
return
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
// From GitHub version/fork maintained by Stephen Paul Weber available at:
|
||||
// https://github.com/singpolyma/mnemonicode
|
||||
//
|
||||
// Originally from:
|
||||
// http://web.archive.org/web/20101031205747/http://www.tothink.com/mnemonic/
|
||||
|
||||
/*
|
||||
Copyright (c) 2000 Oren Tirosh <oren@hishome.net>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
|
@ -0,0 +1,118 @@
|
|||
Mnemonicode
|
||||
===========
|
||||
|
||||
Mnemonicode is a method for encoding binary data into a sequence
|
||||
of words which can be spoken over the phone, for example, and converted
|
||||
back to data on the other side.
|
||||
|
||||
[![GoDoc](https://godoc.org/bitbucket.org/dchapes/mnemonicode?status.png)](https://godoc.org/bitbucket.org/dchapes/mnemonicode)
|
||||
|
||||
Online package documentation is available via
|
||||
[https://godoc.org/bitbucket.org/dchapes/mnemonicode](https://godoc.org/bitbucket.org/dchapes/mnemonicode).
|
||||
|
||||
To install the package:
|
||||
|
||||
go get bitbucket.org/dchapes/mnemonicode
|
||||
|
||||
or the command line programs:
|
||||
|
||||
go get bitbucket.org/dchapes/mnemonicode/cmd/...
|
||||
|
||||
or `go build` any Go code that imports it:
|
||||
|
||||
import "bitbucket.org/dchapes/mnemonicode"
|
||||
|
||||
For more information see
|
||||
<https://github.com/singpolyma/mnemonicode>
|
||||
or
|
||||
<http://web.archive.org/web/20101031205747/http://www.tothink.com/mnemonic/>
|
||||
|
||||
From the README there:
|
||||
|
||||
There are some other somewhat similar systems that seem less satisfactory:
|
||||
|
||||
- OTP was designed for easy typing, and for minimizing length, but as
|
||||
a consequence the word list contains words that are similar ("AD"
|
||||
and "ADD") that are poor for dictating over the phone
|
||||
|
||||
- PGPfone has optimized "maximum phonetic distance" between words,
|
||||
which resolves the above problem but has some other drawbacks:
|
||||
|
||||
- Low efficiency, as it encodes a little less than 1 bit per
|
||||
character;
|
||||
|
||||
- Word quality issues, as some words are somewhat obscure to
|
||||
non-native speakers of English, or are awkward to use or type.
|
||||
|
||||
Mnemonic tries to do better by being more selective about its word
|
||||
list. Its criteria are thus:
|
||||
|
||||
Mandatory Criteria:
|
||||
|
||||
- The wordlist contains 1626 words.
|
||||
|
||||
- All words are between 4 and 7 letters long.
|
||||
|
||||
- No word in the list is a prefix of another word (e.g. visit,
|
||||
visitor).
|
||||
|
||||
- Five letter prefixes of words are sufficient to be unique.
|
||||
|
||||
Less Strict Criteria:
|
||||
|
||||
- The words should be usable by people all over the world. The list
|
||||
is far from perfect in that respect. It is heavily biased towards
|
||||
western culture and English in particular. The international
|
||||
vocabulary is simply not big enough. One can argue that even words
|
||||
like "hotel" or "radio" are not truly international. You will find
|
||||
many English words in the list but I have tried to limit them to
|
||||
words that are part of a beginner's vocabulary or words that have
|
||||
close relatives in other european languages. In some cases a word
|
||||
has a different meaning in another language or is pronounced very
|
||||
differently but for the purpose of the encoding it is still ok - I
|
||||
assume that when the encoding is used for spoken communication
|
||||
both sides speak the same language.
|
||||
|
||||
- The words should have more than one syllable. This makes them
|
||||
easier to recognize when spoken, especially over a phone
|
||||
line. Again, you will find many exceptions. For one syllable words
|
||||
I have tried to use words with 3 or more consonants or words with
|
||||
diphthongs, making for a longer and more distinct
|
||||
pronounciation. As a result of this requirement the average word
|
||||
length has increased. I do not consider this to be a problem since
|
||||
my goal in limiting the word length was not to reduce the average
|
||||
length of encoded data but to limit the maximum length to fit in
|
||||
fixed-size fields or a terminal line width.
|
||||
|
||||
- No two words on the list should sound too much alike. Soundalikes
|
||||
such as "sweet" and "suite" are ruled out. One of the two is
|
||||
chosen and the other should be accepted by the decoder's
|
||||
soundalike matching code or using explicit aliases for some words.
|
||||
|
||||
- No offensive words. The rule was to avoid words that I would not
|
||||
like to be printed on my business card. I have extended this to
|
||||
words that by themselves are not offensive but are too likely to
|
||||
create combinations that someone may find embarrassing or
|
||||
offensive. This includes words dealing with religion such as
|
||||
"church" or "jewish" and some words with negative meanings like
|
||||
"problem" or "fiasco". I am sure that a creative mind (or a random
|
||||
number generator) can find plenty of embarrasing or offensive word
|
||||
combinations using only words in the list but I have tried to
|
||||
avoid the more obvious ones. One of my tools for this was simply a
|
||||
generator of random word combinations - the problematic ones stick
|
||||
out like a sore thumb.
|
||||
|
||||
- Avoid words with tricky spelling or pronounciation. Even if the
|
||||
receiver of the message can probably spell the word close enough
|
||||
for the soundalike matcher to recognize it correctly I prefer
|
||||
avoiding such words. I believe this will help users feel more
|
||||
comfortable using the system, increase the level of confidence and
|
||||
decrease the overall error rate. Most words in the list can be
|
||||
spelled more or less correctly from hearing, even without knowing
|
||||
the word.
|
||||
|
||||
- The word should feel right for the job. I know, this one is very
|
||||
subjective but some words would meet all the criteria and still
|
||||
not feel right for the purpose of mnemonic encoding. The word
|
||||
should feel like one of the words in the radio phonetic alphabets
|
||||
(alpha, bravo, charlie, delta etc).
|
|
@ -0,0 +1,70 @@
|
|||
// For use with go-fuzz, "github.com/dvyukov/go-fuzz"
|
||||
//
|
||||
// +build gofuzz
|
||||
|
||||
package mnemonicode
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/text/transform"
|
||||
)
|
||||
|
||||
var (
|
||||
tenc = NewEncodeTransformer(nil)
|
||||
tdec = NewDecodeTransformer()
|
||||
tencdec = transform.Chain(tenc, tdec)
|
||||
)
|
||||
|
||||
//go:generate go-fuzz-build bitbucket.org/dchapes/mnemonicode
|
||||
// Then:
|
||||
// go-fuzz -bin=mnemonicode-fuzz.zip -workdir=fuzz
|
||||
|
||||
// Fuzz is for use with go-fuzz, "github.com/dvyukov/go-fuzz"
|
||||
func Fuzz(data []byte) int {
|
||||
words := EncodeWordList(nil, data)
|
||||
if len(words) != WordsRequired(len(data)) {
|
||||
panic("bad WordsRequired result")
|
||||
}
|
||||
data2, err := DecodeWordList(nil, words)
|
||||
if err != nil {
|
||||
fmt.Println("words:", words)
|
||||
panic(err)
|
||||
}
|
||||
if !bytes.Equal(data, data2) {
|
||||
fmt.Println("words:", words)
|
||||
panic("data != data2")
|
||||
}
|
||||
|
||||
data3, _, err := transform.Bytes(tencdec, data)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if !bytes.Equal(data, data3) {
|
||||
fmt.Println("words:", words)
|
||||
panic("data != data3")
|
||||
}
|
||||
|
||||
if len(data) == 0 {
|
||||
return 0
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
//go:generate go-fuzz-build -func Fuzz2 -o mnemonicode-fuzz2.zip bitbucket.org/dchapes/mnemonicode
|
||||
// Then:
|
||||
// go-fuzz -bin=mnemonicode-fuzz2.zip -workdir=fuzz2
|
||||
|
||||
// Fuzz2 is another fuzz tester, this time with words as input rather than binary data.
|
||||
func Fuzz2(data []byte) int {
|
||||
_, _, err := transform.Bytes(tdec, data)
|
||||
if err != nil {
|
||||
if _, ok := err.(WordError); !ok {
|
||||
return 0
|
||||
}
|
||||
fmt.Println("Unexpected error")
|
||||
panic(err)
|
||||
}
|
||||
return 1
|
||||
}
|
|
@ -0,0 +1,557 @@
|
|||
// Package mnemonicode …
|
||||
package mnemonicode
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"golang.org/x/text/transform"
|
||||
)
|
||||
|
||||
// WordsRequired returns the number of words required to encode input
|
||||
// data of length bytes using mnomonic encoding.
|
||||
//
|
||||
// Every four bytes of input is encoded into three words. If there
|
||||
// is an extra one or two bytes they get an extra one or two words
|
||||
// respectively. If there is an extra three bytes, they will be encoded
|
||||
// into three words with the last word being one of a small set of very
|
||||
// short words (only needed to encode the last 3 bits).
|
||||
func WordsRequired(length int) int {
|
||||
return ((length + 1) * 3) / 4
|
||||
}
|
||||
|
||||
// A Config structure contains options for mneomonic encoding.
|
||||
//
|
||||
// {PREFIX}word{wsep}word{gsep}word{wsep}word{SUFFIX}
|
||||
type Config struct {
|
||||
LinePrefix string
|
||||
LineSuffix string
|
||||
WordSeparator string
|
||||
GroupSeparator string
|
||||
WordsPerGroup uint
|
||||
GroupsPerLine uint
|
||||
WordPadding rune
|
||||
}
|
||||
|
||||
var defaultConfig = Config{
|
||||
LinePrefix: "",
|
||||
LineSuffix: "\n",
|
||||
WordSeparator: " ",
|
||||
GroupSeparator: " - ",
|
||||
WordsPerGroup: 3,
|
||||
GroupsPerLine: 3,
|
||||
WordPadding: ' ',
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a newly allocated Config initialised with default values.
|
||||
func NewDefaultConfig() *Config {
|
||||
r := new(Config)
|
||||
*r = defaultConfig
|
||||
return r
|
||||
}
|
||||
|
||||
// NewEncodeReader returns a new io.Reader that will return a
|
||||
// formatted list of mnemonic words representing the bytes in r.
|
||||
//
|
||||
// The configuration of the word formatting is controlled
|
||||
// by c, which can be nil for default formatting.
|
||||
func NewEncodeReader(r io.Reader, c *Config) io.Reader {
|
||||
t := NewEncodeTransformer(c)
|
||||
return transform.NewReader(r, t)
|
||||
}
|
||||
|
||||
// NewEncoder returns a new io.WriteCloser that will write a formatted
|
||||
// list of mnemonic words representing the bytes written to w. The user
|
||||
// needs to call Close to flush unwritten bytes that may be buffered.
|
||||
//
|
||||
// The configuration of the word formatting is controlled
|
||||
// by c, which can be nil for default formatting.
|
||||
func NewEncoder(w io.Writer, c *Config) io.WriteCloser {
|
||||
t := NewEncodeTransformer(c)
|
||||
return transform.NewWriter(w, t)
|
||||
}
|
||||
|
||||
// NewEncodeTransformer returns a new transformer
|
||||
// that encodes bytes into mnemonic words.
|
||||
//
|
||||
// The configuration of the word formatting is controlled
|
||||
// by c, which can be nil for default formatting.
|
||||
func NewEncodeTransformer(c *Config) transform.Transformer {
|
||||
if c == nil {
|
||||
c = &defaultConfig
|
||||
}
|
||||
return &enctrans{
|
||||
c: *c,
|
||||
state: needPrefix,
|
||||
}
|
||||
}
|
||||
|
||||
type enctrans struct {
|
||||
c Config
|
||||
state encTransState
|
||||
wordCnt uint
|
||||
groupCnt uint
|
||||
wordidx [3]int
|
||||
wordidxcnt int // remaining indexes in wordidx; wordidx[3-wordidxcnt:]
|
||||
}
|
||||
|
||||
func (t *enctrans) Reset() {
|
||||
t.state = needPrefix
|
||||
t.wordCnt = 0
|
||||
t.groupCnt = 0
|
||||
t.wordidxcnt = 0
|
||||
}
|
||||
|
||||
type encTransState uint8
|
||||
|
||||
const (
|
||||
needNothing = iota
|
||||
needPrefix
|
||||
needWordSep
|
||||
needGroupSep
|
||||
needSuffix
|
||||
)
|
||||
|
||||
func (t *enctrans) strState() (str string, nextState encTransState) {
|
||||
switch t.state {
|
||||
case needPrefix:
|
||||
str = t.c.LinePrefix
|
||||
case needWordSep:
|
||||
str = t.c.WordSeparator
|
||||
case needGroupSep:
|
||||
str = t.c.GroupSeparator
|
||||
case needSuffix:
|
||||
str = t.c.LineSuffix
|
||||
nextState = needPrefix
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (t *enctrans) advState() {
|
||||
t.wordCnt++
|
||||
if t.wordCnt < t.c.WordsPerGroup {
|
||||
t.state = needWordSep
|
||||
} else {
|
||||
t.wordCnt = 0
|
||||
t.groupCnt++
|
||||
if t.groupCnt < t.c.GroupsPerLine {
|
||||
t.state = needGroupSep
|
||||
} else {
|
||||
t.groupCnt = 0
|
||||
t.state = needSuffix
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// transformWords consumes words from wordidx copying the words with
|
||||
// formatting into dst.
|
||||
// On return, if err==nil, all words were consumed (wordidxcnt==0).
|
||||
func (t *enctrans) transformWords(dst []byte) (nDst int, err error) {
|
||||
//log.Println("transformWords: len(dst)=",len(dst),"wordidxcnt=",t.wordidxcnt)
|
||||
for t.wordidxcnt > 0 {
|
||||
for t.state != needNothing {
|
||||
str, nextState := t.strState()
|
||||
if len(dst) < len(str) {
|
||||
return nDst, transform.ErrShortDst
|
||||
}
|
||||
n := copy(dst, str)
|
||||
dst = dst[n:]
|
||||
nDst += n
|
||||
t.state = nextState
|
||||
}
|
||||
word := wordList[t.wordidx[3-t.wordidxcnt]]
|
||||
n := len(word)
|
||||
if n < longestWord {
|
||||
if rlen := utf8.RuneLen(t.c.WordPadding); rlen > 0 {
|
||||
n += (longestWord - n) * rlen
|
||||
}
|
||||
}
|
||||
if len(dst) < n {
|
||||
return nDst, transform.ErrShortDst
|
||||
}
|
||||
n = copy(dst, word)
|
||||
t.wordidxcnt--
|
||||
dst = dst[n:]
|
||||
nDst += n
|
||||
if t.c.WordPadding != 0 {
|
||||
for i := n; i < longestWord; i++ {
|
||||
n = utf8.EncodeRune(dst, t.c.WordPadding)
|
||||
dst = dst[n:]
|
||||
nDst += n
|
||||
}
|
||||
}
|
||||
t.advState()
|
||||
}
|
||||
return nDst, nil
|
||||
}
|
||||
|
||||
// Transform implements the transform.Transformer interface.
|
||||
func (t *enctrans) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
|
||||
//log.Printf("Transform(%d,%d,%t)\n", len(dst), len(src), atEOF)
|
||||
var n int
|
||||
for {
|
||||
if t.wordidxcnt > 0 {
|
||||
n, err = t.transformWords(dst)
|
||||
dst = dst[n:]
|
||||
nDst += n
|
||||
if err != nil {
|
||||
//log.Printf("\t\t\tRet1: (%d) %d, %d, %v\n", t.wordidxcnt, nDst, nSrc, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
var x uint32
|
||||
switch {
|
||||
case len(src) >= 4:
|
||||
x = uint32(src[0])
|
||||
x |= uint32(src[1]) << 8
|
||||
x |= uint32(src[2]) << 16
|
||||
x |= uint32(src[3]) << 24
|
||||
src = src[4:]
|
||||
nSrc += 4
|
||||
|
||||
t.wordidx[0] = int(x % base)
|
||||
t.wordidx[1] = int(x/base) % base
|
||||
t.wordidx[2] = int(x/base/base) % base
|
||||
t.wordidxcnt = 3
|
||||
//log.Printf("\t\tConsumed 4 bytes (%d, %d)", nDst, nSrc)
|
||||
//continue
|
||||
case len(src) == 0:
|
||||
//log.Printf("\t\t\tRet2: (%d) %d, %d, %v\n", t.wordidxcnt, nDst, nSrc, err)
|
||||
return
|
||||
case !atEOF:
|
||||
//log.Printf("\t\t!atEOF (%d, %d)", nDst, nSrc)
|
||||
err = transform.ErrShortSrc
|
||||
return
|
||||
default:
|
||||
x = 0
|
||||
n = len(src)
|
||||
for i := n - 1; i >= 0; i-- {
|
||||
x <<= 8
|
||||
x |= uint32(src[i])
|
||||
}
|
||||
t.wordidx[3-n] = int(x % base)
|
||||
if n >= 2 {
|
||||
t.wordidx[4-n] = int(x/base) % base
|
||||
}
|
||||
if n == 3 {
|
||||
t.wordidx[2] = base + int(x/base/base)%7
|
||||
}
|
||||
src = src[n:]
|
||||
nSrc += n
|
||||
t.wordidxcnt = n
|
||||
//log.Printf("\t\tatEOF (%d) (%d, %d)", t.wordidxcnt, nDst, nSrc)
|
||||
//continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
// NewDecoder returns a new io.Reader that will return the
|
||||
// decoded bytes from mnemonic words in r. Unrecognized
|
||||
// words in r will cause reads to return an error.
|
||||
func NewDecoder(r io.Reader) io.Reader {
|
||||
t := NewDecodeTransformer()
|
||||
return transform.NewReader(r, t)
|
||||
}
|
||||
|
||||
// NewDecodeWriter returns a new io.WriteCloser that will
|
||||
// write decoded bytes from mnemonic words written to it.
|
||||
// Unrecognized words will cause a write error. The user needs
|
||||
// to call Close to flush unwritten bytes that may be buffered.
|
||||
func NewDecodeWriter(w io.Writer) io.WriteCloser {
|
||||
t := NewDecodeTransformer()
|
||||
return transform.NewWriter(w, t)
|
||||
}
|
||||
|
||||
// NewDecodeTransformer returns a new transform
|
||||
// that decodes mnemonic words into the represented
|
||||
// bytes. Unrecognized words will trigger an error.
|
||||
func NewDecodeTransformer() transform.Transformer {
|
||||
return &dectrans{wordidx: make([]int, 0, 3)}
|
||||
}
|
||||
|
||||
type dectrans struct {
|
||||
wordidx []int
|
||||
short bool // last word in wordidx is/was short
|
||||
}
|
||||
|
||||
func (t *dectrans) Reset() {
|
||||
t.wordidx = nil
|
||||
t.short = false
|
||||
}
|
||||
|
||||
func (t *dectrans) transformWords(dst []byte) (int, error) {
|
||||
//log.Println("transformWords: len(dst)=",len(dst),"len(t.wordidx)=", len(t.wordidx))
|
||||
n := len(t.wordidx)
|
||||
if n == 3 && !t.short {
|
||||
n = 4
|
||||
}
|
||||
if len(dst) < n {
|
||||
return 0, transform.ErrShortDst
|
||||
}
|
||||
for len(t.wordidx) < 3 {
|
||||
t.wordidx = append(t.wordidx, 0)
|
||||
}
|
||||
x := uint32(t.wordidx[2])
|
||||
x *= base
|
||||
x += uint32(t.wordidx[1])
|
||||
x *= base
|
||||
x += uint32(t.wordidx[0])
|
||||
for i := 0; i < n; i++ {
|
||||
dst[i] = byte(x)
|
||||
x >>= 8
|
||||
}
|
||||
t.wordidx = t.wordidx[:0]
|
||||
return n, nil
|
||||
}
|
||||
|
||||
type WordError interface {
|
||||
error
|
||||
Word() string
|
||||
}
|
||||
|
||||
type UnexpectedWordError string
|
||||
type UnexpectedEndWordError string
|
||||
type UnknownWordError string
|
||||
|
||||
func (e UnexpectedWordError) Word() string { return string(e) }
|
||||
func (e UnexpectedEndWordError) Word() string { return string(e) }
|
||||
func (e UnknownWordError) Word() string { return string(e) }
|
||||
func (e UnexpectedWordError) Error() string {
|
||||
return fmt.Sprintf("mnemonicode: unexpected word after short word: %q", string(e))
|
||||
}
|
||||
func (e UnexpectedEndWordError) Error() string {
|
||||
return fmt.Sprintf("mnemonicode: unexpected end word: %q", string(e))
|
||||
}
|
||||
func (e UnknownWordError) Error() string {
|
||||
return fmt.Sprintf("mnemonicode: unknown word: %q", string(e))
|
||||
}
|
||||
|
||||
// Transform implements the transform.Transformer interface.
|
||||
func (t *dectrans) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
|
||||
//log.Printf("Transform(%d,%d,%t)\n", len(dst), len(src), atEOF)
|
||||
var n int
|
||||
for len(t.wordidx) > 0 || len(src) > 0 {
|
||||
for len(t.wordidx) < 3 {
|
||||
var word []byte
|
||||
var idx int
|
||||
//n, word, err = bufio.ScanWords(src, atEOF)
|
||||
n, word, err = scanWords(src, atEOF)
|
||||
src = src[n:]
|
||||
nSrc += n
|
||||
if err != nil {
|
||||
//log.Print("ScanWords error:", err)
|
||||
return
|
||||
}
|
||||
if word == nil {
|
||||
if atEOF {
|
||||
//log.Printf("atEOF (%d, %d) %d, %d", nDst, nSrc, n, len(src))
|
||||
n = len(src)
|
||||
src = src[n:]
|
||||
nSrc += n
|
||||
break
|
||||
}
|
||||
//log.Printf("\t\t!atEOF (%d, %d)", nDst, nSrc)
|
||||
err = transform.ErrShortSrc
|
||||
return
|
||||
}
|
||||
if t.short {
|
||||
err = UnexpectedWordError(word)
|
||||
//log.Print("short error:", err)
|
||||
return
|
||||
}
|
||||
idx, _, t.short, err = closestWordIdx(string(word), len(t.wordidx) == 2)
|
||||
if err != nil {
|
||||
//log.Print("closestWordIdx error:", err)
|
||||
return
|
||||
}
|
||||
t.wordidx = append(t.wordidx, idx)
|
||||
}
|
||||
if len(t.wordidx) > 0 {
|
||||
n, err = t.transformWords(dst)
|
||||
dst = dst[n:]
|
||||
nDst += n
|
||||
if n != 4 {
|
||||
//log.Println("transformWords returned:", n, err)
|
||||
//log.Println("len(t.wordidx):", len(t.wordidx), len(src))
|
||||
}
|
||||
if err != nil {
|
||||
//log.Printf("\t\t\tRet1: (%d) %d, %d, %v\n", len(t.wordidx), nDst, nSrc, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
const base = 1626
|
||||
|
||||
// EncodeWordList encodes src into mnemomic words which are appended to dst.
|
||||
// The final wordlist is returned.
|
||||
// There will be WordsRequired(len(src)) words appeneded.
|
||||
func EncodeWordList(dst []string, src []byte) (result []string) {
|
||||
if n := len(dst) + WordsRequired(len(src)); cap(dst) < n {
|
||||
result = make([]string, len(dst), n)
|
||||
copy(result, dst)
|
||||
} else {
|
||||
result = dst
|
||||
}
|
||||
|
||||
var x uint32
|
||||
for len(src) >= 4 {
|
||||
x = uint32(src[0])
|
||||
x |= uint32(src[1]) << 8
|
||||
x |= uint32(src[2]) << 16
|
||||
x |= uint32(src[3]) << 24
|
||||
src = src[4:]
|
||||
|
||||
i0 := int(x % base)
|
||||
i1 := int(x/base) % base
|
||||
i2 := int(x/base/base) % base
|
||||
result = append(result, wordList[i0], wordList[i1], wordList[i2])
|
||||
}
|
||||
if len(src) > 0 {
|
||||
x = 0
|
||||
for i := len(src) - 1; i >= 0; i-- {
|
||||
x <<= 8
|
||||
x |= uint32(src[i])
|
||||
}
|
||||
i := int(x % base)
|
||||
result = append(result, wordList[i])
|
||||
if len(src) >= 2 {
|
||||
i = int(x/base) % base
|
||||
result = append(result, wordList[i])
|
||||
}
|
||||
if len(src) == 3 {
|
||||
i = base + int(x/base/base)%7
|
||||
result = append(result, wordList[i])
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func closestWordIdx(word string, shortok bool) (idx int, exact, short bool, err error) {
|
||||
word = strings.ToLower(word)
|
||||
if idx, exact = wordMap[word]; !exact {
|
||||
// TODO(dchapes): normalize unicode, remove accents, etc
|
||||
// TODO(dchapes): phonetic algorithm or other closest match
|
||||
err = UnknownWordError(word)
|
||||
return
|
||||
}
|
||||
if short = (idx >= base); short {
|
||||
idx -= base
|
||||
if !shortok {
|
||||
err = UnexpectedEndWordError(word)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DecodeWordList decodes the mnemonic words in src into bytes which are
|
||||
// appended to dst.
|
||||
func DecodeWordList(dst []byte, src []string) (result []byte, err error) {
|
||||
if n := (len(src)+2)/3*4 + len(dst); cap(dst) < n {
|
||||
result = make([]byte, len(dst), n)
|
||||
copy(result, dst)
|
||||
} else {
|
||||
result = dst
|
||||
}
|
||||
|
||||
var idx [3]int
|
||||
for len(src) > 3 {
|
||||
if idx[0], _, _, err = closestWordIdx(src[0], false); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if idx[1], _, _, err = closestWordIdx(src[1], false); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if idx[2], _, _, err = closestWordIdx(src[2], false); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
src = src[3:]
|
||||
x := uint32(idx[2])
|
||||
x *= base
|
||||
x += uint32(idx[1])
|
||||
x *= base
|
||||
x += uint32(idx[0])
|
||||
result = append(result, byte(x), byte(x>>8), byte(x>>16), byte(x>>24))
|
||||
}
|
||||
|
||||
if len(src) > 0 {
|
||||
var short bool
|
||||
idx[1] = 0
|
||||
idx[2] = 0
|
||||
n := len(src)
|
||||
for i := 0; i < n; i++ {
|
||||
idx[i], _, short, err = closestWordIdx(src[i], i == 2)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
x := uint32(idx[2])
|
||||
x *= base
|
||||
x += uint32(idx[1])
|
||||
x *= base
|
||||
x += uint32(idx[0])
|
||||
result = append(result, byte(x))
|
||||
if n > 1 {
|
||||
result = append(result, byte(x>>8))
|
||||
}
|
||||
if n > 2 {
|
||||
result = append(result, byte(x>>16))
|
||||
if !short {
|
||||
result = append(result, byte(x>>24))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
for len(src) > 0 {
|
||||
short := false
|
||||
n := len(src)
|
||||
if n > 3 {
|
||||
n = 3
|
||||
}
|
||||
for i := 0; i < n; i++ {
|
||||
idx[i], _, err = closestWordIdx(src[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if idx[i] >= base {
|
||||
if i != 2 || len(src) != 3 {
|
||||
return nil, UnexpectedEndWord(src[i])
|
||||
}
|
||||
short = true
|
||||
idx[i] -= base
|
||||
}
|
||||
}
|
||||
for i := n; i < 3; i++ {
|
||||
idx[i] = 0
|
||||
}
|
||||
src = src[n:]
|
||||
x := uint32(idx[2])
|
||||
x *= base
|
||||
x += uint32(idx[1])
|
||||
x *= base
|
||||
x += uint32(idx[0])
|
||||
result = append(result, byte(x))
|
||||
if n > 1 {
|
||||
result = append(result, byte(x>>8))
|
||||
}
|
||||
if n > 2 {
|
||||
result = append(result, byte(x>>16))
|
||||
if !short {
|
||||
result = append(result, byte(x>>24))
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
return result, nil
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package mnemonicode
|
||||
|
||||
import (
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// modified version of bufio.ScanWords from bufio/scan.go
|
||||
|
||||
// scanWords is a split function for a Scanner that returns
|
||||
// each non-letter separated word of text, with surrounding
|
||||
// non-leters deleted. It will never return an empty string.
|
||||
// The definition of letter is set by unicode.IsLetter.
|
||||
func scanWords(data []byte, atEOF bool) (advance int, token []byte, err error) {
|
||||
// Skip leading non-letters.
|
||||
start := 0
|
||||
for width := 0; start < len(data); start += width {
|
||||
var r rune
|
||||
r, width = utf8.DecodeRune(data[start:])
|
||||
if unicode.IsLetter(r) {
|
||||
break
|
||||
}
|
||||
}
|
||||
if atEOF && len(data) == 0 {
|
||||
return 0, nil, nil
|
||||
}
|
||||
// Scan until non-letter, marking end of word.
|
||||
for width, i := 0, start; i < len(data); i += width {
|
||||
var r rune
|
||||
r, width = utf8.DecodeRune(data[i:])
|
||||
if !unicode.IsLetter(r) {
|
||||
return i + width, data[start:i], nil
|
||||
}
|
||||
}
|
||||
// If we're at EOF, we have a final, non-empty, non-terminated word. Return it.
|
||||
if atEOF && len(data) > start {
|
||||
return len(data), data[start:], nil
|
||||
}
|
||||
// Request more data.
|
||||
return 0, nil, nil
|
||||
}
|
|
@ -0,0 +1,290 @@
|
|||
package mnemonicode
|
||||
|
||||
// WordListVersion is the version of compiled in word list.
|
||||
const WordListVersion = "0.7"
|
||||
|
||||
var wordMap = make(map[string]int, len(wordList))
|
||||
|
||||
func init() {
|
||||
for i, w := range wordList {
|
||||
wordMap[w] = i
|
||||
}
|
||||
}
|
||||
|
||||
const longestWord = 7
|
||||
|
||||
var wordList = []string{
|
||||
"academy", "acrobat", "active", "actor", "adam", "admiral",
|
||||
"adrian", "africa", "agenda", "agent", "airline", "airport",
|
||||
"aladdin", "alarm", "alaska", "albert", "albino", "album",
|
||||
"alcohol", "alex", "algebra", "alibi", "alice", "alien",
|
||||
"alpha", "alpine", "amadeus", "amanda", "amazon", "amber",
|
||||
"america", "amigo", "analog", "anatomy", "angel", "animal",
|
||||
"antenna", "antonio", "apollo", "april", "archive", "arctic",
|
||||
"arizona", "arnold", "aroma", "arthur", "artist", "asia",
|
||||
"aspect", "aspirin", "athena", "athlete", "atlas", "audio",
|
||||
"august", "austria", "axiom", "aztec", "balance", "ballad",
|
||||
"banana", "bandit", "banjo", "barcode", "baron", "basic",
|
||||
"battery", "belgium", "berlin", "bermuda", "bernard", "bikini",
|
||||
"binary", "bingo", "biology", "block", "blonde", "bonus",
|
||||
"boris", "boston", "boxer", "brandy", "bravo", "brazil",
|
||||
"bronze", "brown", "bruce", "bruno", "burger", "burma",
|
||||
"cabinet", "cactus", "cafe", "cairo", "cake", "calypso",
|
||||
"camel", "camera", "campus", "canada", "canal", "cannon",
|
||||
"canoe", "cantina", "canvas", "canyon", "capital", "caramel",
|
||||
"caravan", "carbon", "cargo", "carlo", "carol", "carpet",
|
||||
"cartel", "casino", "castle", "castro", "catalog", "caviar",
|
||||
"cecilia", "cement", "center", "century", "ceramic", "chamber",
|
||||
"chance", "change", "chaos", "charlie", "charm", "charter",
|
||||
"chef", "chemist", "cherry", "chess", "chicago", "chicken",
|
||||
"chief", "china", "cigar", "cinema", "circus", "citizen",
|
||||
"city", "clara", "classic", "claudia", "clean", "client",
|
||||
"climax", "clinic", "clock", "club", "cobra", "coconut",
|
||||
"cola", "collect", "colombo", "colony", "color", "combat",
|
||||
"comedy", "comet", "command", "compact", "company", "complex",
|
||||
"concept", "concert", "connect", "consul", "contact", "context",
|
||||
"contour", "control", "convert", "copy", "corner", "corona",
|
||||
"correct", "cosmos", "couple", "courage", "cowboy", "craft",
|
||||
"crash", "credit", "cricket", "critic", "crown", "crystal",
|
||||
"cuba", "culture", "dallas", "dance", "daniel", "david",
|
||||
"decade", "decimal", "deliver", "delta", "deluxe", "demand",
|
||||
"demo", "denmark", "derby", "design", "detect", "develop",
|
||||
"diagram", "dialog", "diamond", "diana", "diego", "diesel",
|
||||
"diet", "digital", "dilemma", "diploma", "direct", "disco",
|
||||
"disney", "distant", "doctor", "dollar", "dominic", "domino",
|
||||
"donald", "dragon", "drama", "dublin", "duet", "dynamic",
|
||||
"east", "ecology", "economy", "edgar", "egypt", "elastic",
|
||||
"elegant", "element", "elite", "elvis", "email", "energy",
|
||||
"engine", "english", "episode", "equator", "escort", "ethnic",
|
||||
"europe", "everest", "evident", "exact", "example", "exit",
|
||||
"exotic", "export", "express", "extra", "fabric", "factor",
|
||||
"falcon", "family", "fantasy", "fashion", "fiber", "fiction",
|
||||
"fidel", "fiesta", "figure", "film", "filter", "final",
|
||||
"finance", "finish", "finland", "flash", "florida", "flower",
|
||||
"fluid", "flute", "focus", "ford", "forest", "formal",
|
||||
"format", "formula", "fortune", "forum", "fragile", "france",
|
||||
"frank", "friend", "frozen", "future", "gabriel", "galaxy",
|
||||
"gallery", "gamma", "garage", "garden", "garlic", "gemini",
|
||||
"general", "genetic", "genius", "germany", "global", "gloria",
|
||||
"golf", "gondola", "gong", "good", "gordon", "gorilla",
|
||||
"grand", "granite", "graph", "green", "group", "guide",
|
||||
"guitar", "guru", "hand", "happy", "harbor", "harmony",
|
||||
"harvard", "havana", "hawaii", "helena", "hello", "henry",
|
||||
"hilton", "history", "horizon", "hotel", "human", "humor",
|
||||
"icon", "idea", "igloo", "igor", "image", "impact",
|
||||
"import", "index", "india", "indigo", "input", "insect",
|
||||
"instant", "iris", "italian", "jacket", "jacob", "jaguar",
|
||||
"janet", "japan", "jargon", "jazz", "jeep", "john",
|
||||
"joker", "jordan", "jumbo", "june", "jungle", "junior",
|
||||
"jupiter", "karate", "karma", "kayak", "kermit", "kilo",
|
||||
"king", "koala", "korea", "labor", "lady", "lagoon",
|
||||
"laptop", "laser", "latin", "lava", "lecture", "left",
|
||||
"legal", "lemon", "level", "lexicon", "liberal", "libra",
|
||||
"limbo", "limit", "linda", "linear", "lion", "liquid",
|
||||
"liter", "little", "llama", "lobby", "lobster", "local",
|
||||
"logic", "logo", "lola", "london", "lotus", "lucas",
|
||||
"lunar", "machine", "macro", "madam", "madonna", "madrid",
|
||||
"maestro", "magic", "magnet", "magnum", "major", "mama",
|
||||
"mambo", "manager", "mango", "manila", "marco", "marina",
|
||||
"market", "mars", "martin", "marvin", "master", "matrix",
|
||||
"maximum", "media", "medical", "mega", "melody", "melon",
|
||||
"memo", "mental", "mentor", "menu", "mercury", "message",
|
||||
"metal", "meteor", "meter", "method", "metro", "mexico",
|
||||
"miami", "micro", "million", "mineral", "minimum", "minus",
|
||||
"minute", "miracle", "mirage", "miranda", "mister", "mixer",
|
||||
"mobile", "model", "modem", "modern", "modular", "moment",
|
||||
"monaco", "monica", "monitor", "mono", "monster", "montana",
|
||||
"morgan", "motel", "motif", "motor", "mozart", "multi",
|
||||
"museum", "music", "mustang", "natural", "neon", "nepal",
|
||||
"neptune", "nerve", "neutral", "nevada", "news", "ninja",
|
||||
"nirvana", "normal", "nova", "novel", "nuclear", "numeric",
|
||||
"nylon", "oasis", "object", "observe", "ocean", "octopus",
|
||||
"olivia", "olympic", "omega", "opera", "optic", "optimal",
|
||||
"orange", "orbit", "organic", "orient", "origin", "orlando",
|
||||
"oscar", "oxford", "oxygen", "ozone", "pablo", "pacific",
|
||||
"pagoda", "palace", "pamela", "panama", "panda", "panel",
|
||||
"panic", "paradox", "pardon", "paris", "parker", "parking",
|
||||
"parody", "partner", "passage", "passive", "pasta", "pastel",
|
||||
"patent", "patriot", "patrol", "patron", "pegasus", "pelican",
|
||||
"penguin", "pepper", "percent", "perfect", "perfume", "period",
|
||||
"permit", "person", "peru", "phone", "photo", "piano",
|
||||
"picasso", "picnic", "picture", "pigment", "pilgrim", "pilot",
|
||||
"pirate", "pixel", "pizza", "planet", "plasma", "plaster",
|
||||
"plastic", "plaza", "pocket", "poem", "poetic", "poker",
|
||||
"polaris", "police", "politic", "polo", "polygon", "pony",
|
||||
"popcorn", "popular", "postage", "postal", "precise", "prefix",
|
||||
"premium", "present", "price", "prince", "printer", "prism",
|
||||
"private", "product", "profile", "program", "project", "protect",
|
||||
"proton", "public", "pulse", "puma", "pyramid", "queen",
|
||||
"radar", "radio", "random", "rapid", "rebel", "record",
|
||||
"recycle", "reflex", "reform", "regard", "regular", "relax",
|
||||
"report", "reptile", "reverse", "ricardo", "ringo", "ritual",
|
||||
"robert", "robot", "rocket", "rodeo", "romeo", "royal",
|
||||
"russian", "safari", "salad", "salami", "salmon", "salon",
|
||||
"salute", "samba", "sandra", "santana", "sardine", "school",
|
||||
"screen", "script", "second", "secret", "section", "segment",
|
||||
"select", "seminar", "senator", "senior", "sensor", "serial",
|
||||
"service", "sheriff", "shock", "sierra", "signal", "silicon",
|
||||
"silver", "similar", "simon", "single", "siren", "slogan",
|
||||
"social", "soda", "solar", "solid", "solo", "sonic",
|
||||
"soviet", "special", "speed", "spiral", "spirit", "sport",
|
||||
"static", "station", "status", "stereo", "stone", "stop",
|
||||
"street", "strong", "student", "studio", "style", "subject",
|
||||
"sultan", "super", "susan", "sushi", "suzuki", "switch",
|
||||
"symbol", "system", "tactic", "tahiti", "talent", "tango",
|
||||
"tarzan", "taxi", "telex", "tempo", "tennis", "texas",
|
||||
"textile", "theory", "thermos", "tiger", "titanic", "tokyo",
|
||||
"tomato", "topic", "tornado", "toronto", "torpedo", "total",
|
||||
"totem", "tourist", "tractor", "traffic", "transit", "trapeze",
|
||||
"travel", "tribal", "trick", "trident", "trilogy", "tripod",
|
||||
"tropic", "trumpet", "tulip", "tuna", "turbo", "twist",
|
||||
"ultra", "uniform", "union", "uranium", "vacuum", "valid",
|
||||
"vampire", "vanilla", "vatican", "velvet", "ventura", "venus",
|
||||
"vertigo", "veteran", "victor", "video", "vienna", "viking",
|
||||
"village", "vincent", "violet", "violin", "virtual", "virus",
|
||||
"visa", "vision", "visitor", "visual", "vitamin", "viva",
|
||||
"vocal", "vodka", "volcano", "voltage", "volume", "voyage",
|
||||
"water", "weekend", "welcome", "western", "window", "winter",
|
||||
"wizard", "wolf", "world", "xray", "yankee", "yoga",
|
||||
"yogurt", "yoyo", "zebra", "zero", "zigzag", "zipper",
|
||||
"zodiac", "zoom", "abraham", "action", "address", "alabama",
|
||||
"alfred", "almond", "ammonia", "analyze", "annual", "answer",
|
||||
"apple", "arena", "armada", "arsenal", "atlanta", "atomic",
|
||||
"avenue", "average", "bagel", "baker", "ballet", "bambino",
|
||||
"bamboo", "barbara", "basket", "bazaar", "benefit", "bicycle",
|
||||
"bishop", "blitz", "bonjour", "bottle", "bridge", "british",
|
||||
"brother", "brush", "budget", "cabaret", "cadet", "candle",
|
||||
"capitan", "capsule", "career", "cartoon", "channel", "chapter",
|
||||
"cheese", "circle", "cobalt", "cockpit", "college", "compass",
|
||||
"comrade", "condor", "crimson", "cyclone", "darwin", "declare",
|
||||
"degree", "delete", "delphi", "denver", "desert", "divide",
|
||||
"dolby", "domain", "domingo", "double", "drink", "driver",
|
||||
"eagle", "earth", "echo", "eclipse", "editor", "educate",
|
||||
"edward", "effect", "electra", "emerald", "emotion", "empire",
|
||||
"empty", "escape", "eternal", "evening", "exhibit", "expand",
|
||||
"explore", "extreme", "ferrari", "first", "flag", "folio",
|
||||
"forget", "forward", "freedom", "fresh", "friday", "fuji",
|
||||
"galileo", "garcia", "genesis", "gold", "gravity", "habitat",
|
||||
"hamlet", "harlem", "helium", "holiday", "house", "hunter",
|
||||
"ibiza", "iceberg", "imagine", "infant", "isotope", "jackson",
|
||||
"jamaica", "jasmine", "java", "jessica", "judo", "kitchen",
|
||||
"lazarus", "letter", "license", "lithium", "loyal", "lucky",
|
||||
"magenta", "mailbox", "manual", "marble", "mary", "maxwell",
|
||||
"mayor", "milk", "monarch", "monday", "money", "morning",
|
||||
"mother", "mystery", "native", "nectar", "nelson", "network",
|
||||
"next", "nikita", "nobel", "nobody", "nominal", "norway",
|
||||
"nothing", "number", "october", "office", "oliver", "opinion",
|
||||
"option", "order", "outside", "package", "pancake", "pandora",
|
||||
"panther", "papa", "patient", "pattern", "pedro", "pencil",
|
||||
"people", "phantom", "philips", "pioneer", "pluto", "podium",
|
||||
"portal", "potato", "prize", "process", "protein", "proxy",
|
||||
"pump", "pupil", "python", "quality", "quarter", "quiet",
|
||||
"rabbit", "radical", "radius", "rainbow", "ralph", "ramirez",
|
||||
"ravioli", "raymond", "respect", "respond", "result", "resume",
|
||||
"retro", "richard", "right", "risk", "river", "roger",
|
||||
"roman", "rondo", "sabrina", "salary", "salsa", "sample",
|
||||
"samuel", "saturn", "savage", "scarlet", "scoop", "scorpio",
|
||||
"scratch", "scroll", "sector", "serpent", "shadow", "shampoo",
|
||||
"sharon", "sharp", "short", "shrink", "silence", "silk",
|
||||
"simple", "slang", "smart", "smoke", "snake", "society",
|
||||
"sonar", "sonata", "soprano", "source", "sparta", "sphere",
|
||||
"spider", "sponsor", "spring", "acid", "adios", "agatha",
|
||||
"alamo", "alert", "almanac", "aloha", "andrea", "anita",
|
||||
"arcade", "aurora", "avalon", "baby", "baggage", "balloon",
|
||||
"bank", "basil", "begin", "biscuit", "blue", "bombay",
|
||||
"brain", "brenda", "brigade", "cable", "carmen", "cello",
|
||||
"celtic", "chariot", "chrome", "citrus", "civil", "cloud",
|
||||
"common", "compare", "cool", "copper", "coral", "crater",
|
||||
"cubic", "cupid", "cycle", "depend", "door", "dream",
|
||||
"dynasty", "edison", "edition", "enigma", "equal", "eric",
|
||||
"event", "evita", "exodus", "extend", "famous", "farmer",
|
||||
"food", "fossil", "frog", "fruit", "geneva", "gentle",
|
||||
"george", "giant", "gilbert", "gossip", "gram", "greek",
|
||||
"grille", "hammer", "harvest", "hazard", "heaven", "herbert",
|
||||
"heroic", "hexagon", "husband", "immune", "inca", "inch",
|
||||
"initial", "isabel", "ivory", "jason", "jerome", "joel",
|
||||
"joshua", "journal", "judge", "juliet", "jump", "justice",
|
||||
"kimono", "kinetic", "leonid", "lima", "maze", "medusa",
|
||||
"member", "memphis", "michael", "miguel", "milan", "mile",
|
||||
"miller", "mimic", "mimosa", "mission", "monkey", "moral",
|
||||
"moses", "mouse", "nancy", "natasha", "nebula", "nickel",
|
||||
"nina", "noise", "orchid", "oregano", "origami", "orinoco",
|
||||
"orion", "othello", "paper", "paprika", "prelude", "prepare",
|
||||
"pretend", "profit", "promise", "provide", "puzzle", "remote",
|
||||
"repair", "reply", "rival", "riviera", "robin", "rose",
|
||||
"rover", "rudolf", "saga", "sahara", "scholar", "shelter",
|
||||
"ship", "shoe", "sigma", "sister", "sleep", "smile",
|
||||
"spain", "spark", "split", "spray", "square", "stadium",
|
||||
"star", "storm", "story", "strange", "stretch", "stuart",
|
||||
"subway", "sugar", "sulfur", "summer", "survive", "sweet",
|
||||
"swim", "table", "taboo", "target", "teacher", "telecom",
|
||||
"temple", "tibet", "ticket", "tina", "today", "toga",
|
||||
"tommy", "tower", "trivial", "tunnel", "turtle", "twin",
|
||||
"uncle", "unicorn", "unique", "update", "valery", "vega",
|
||||
"version", "voodoo", "warning", "william", "wonder", "year",
|
||||
"yellow", "young", "absent", "absorb", "accent", "alfonso",
|
||||
"alias", "ambient", "andy", "anvil", "appear", "apropos",
|
||||
"archer", "ariel", "armor", "arrow", "austin", "avatar",
|
||||
"axis", "baboon", "bahama", "bali", "balsa", "bazooka",
|
||||
"beach", "beast", "beatles", "beauty", "before", "benny",
|
||||
"betty", "between", "beyond", "billy", "bison", "blast",
|
||||
"bless", "bogart", "bonanza", "book", "border", "brave",
|
||||
"bread", "break", "broken", "bucket", "buenos", "buffalo",
|
||||
"bundle", "button", "buzzer", "byte", "caesar", "camilla",
|
||||
"canary", "candid", "carrot", "cave", "chant", "child",
|
||||
"choice", "chris", "cipher", "clarion", "clark", "clever",
|
||||
"cliff", "clone", "conan", "conduct", "congo", "content",
|
||||
"costume", "cotton", "cover", "crack", "current", "danube",
|
||||
"data", "decide", "desire", "detail", "dexter", "dinner",
|
||||
"dispute", "donor", "druid", "drum", "easy", "eddie",
|
||||
"enjoy", "enrico", "epoxy", "erosion", "except", "exile",
|
||||
"explain", "fame", "fast", "father", "felix", "field",
|
||||
"fiona", "fire", "fish", "flame", "flex", "flipper",
|
||||
"float", "flood", "floor", "forbid", "forever", "fractal",
|
||||
"frame", "freddie", "front", "fuel", "gallop", "game",
|
||||
"garbo", "gate", "gibson", "ginger", "giraffe", "gizmo",
|
||||
"glass", "goblin", "gopher", "grace", "gray", "gregory",
|
||||
"grid", "griffin", "ground", "guest", "gustav", "gyro",
|
||||
"hair", "halt", "harris", "heart", "heavy", "herman",
|
||||
"hippie", "hobby", "honey", "hope", "horse", "hostel",
|
||||
"hydro", "imitate", "info", "ingrid", "inside", "invent",
|
||||
"invest", "invite", "iron", "ivan", "james", "jester",
|
||||
"jimmy", "join", "joseph", "juice", "julius", "july",
|
||||
"justin", "kansas", "karl", "kevin", "kiwi", "ladder",
|
||||
"lake", "laura", "learn", "legacy", "legend", "lesson",
|
||||
"life", "light", "list", "locate", "lopez", "lorenzo",
|
||||
"love", "lunch", "malta", "mammal", "margo", "marion",
|
||||
"mask", "match", "mayday", "meaning", "mercy", "middle",
|
||||
"mike", "mirror", "modest", "morph", "morris", "nadia",
|
||||
"nato", "navy", "needle", "neuron", "never", "newton",
|
||||
"nice", "night", "nissan", "nitro", "nixon", "north",
|
||||
"oberon", "octavia", "ohio", "olga", "open", "opus",
|
||||
"orca", "oval", "owner", "page", "paint", "palma",
|
||||
"parade", "parent", "parole", "paul", "peace", "pearl",
|
||||
"perform", "phoenix", "phrase", "pierre", "pinball", "place",
|
||||
"plate", "plato", "plume", "pogo", "point", "polite",
|
||||
"polka", "poncho", "powder", "prague", "press", "presto",
|
||||
"pretty", "prime", "promo", "quasi", "quest", "quick",
|
||||
"quiz", "quota", "race", "rachel", "raja", "ranger",
|
||||
"region", "remark", "rent", "reward", "rhino", "ribbon",
|
||||
"rider", "road", "rodent", "round", "rubber", "ruby",
|
||||
"rufus", "sabine", "saddle", "sailor", "saint", "salt",
|
||||
"satire", "scale", "scuba", "season", "secure", "shake",
|
||||
"shallow", "shannon", "shave", "shelf", "sherman", "shine",
|
||||
"shirt", "side", "sinatra", "sincere", "size", "slalom",
|
||||
"slow", "small", "snow", "sofia", "song", "sound",
|
||||
"south", "speech", "spell", "spend", "spoon", "stage",
|
||||
"stamp", "stand", "state", "stella", "stick", "sting",
|
||||
"stock", "store", "sunday", "sunset", "support", "sweden",
|
||||
"swing", "tape", "think", "thomas", "tictac", "time",
|
||||
"toast", "tobacco", "tonight", "torch", "torso", "touch",
|
||||
"toyota", "trade", "tribune", "trinity", "triton", "truck",
|
||||
"trust", "type", "under", "unit", "urban", "urgent",
|
||||
"user", "value", "vendor", "venice", "verona", "vibrate",
|
||||
"virgo", "visible", "vista", "vital", "voice", "vortex",
|
||||
"waiter", "watch", "wave", "weather", "wedding", "wheel",
|
||||
"whiskey", "wisdom", "deal", "null", "nurse", "quebec",
|
||||
"reserve", "reunion", "roof", "singer", "verbal", "amen",
|
||||
"ego", "fax", "jet", "job", "rio", "ski",
|
||||
"yes",
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue