1
0
mirror of https://github.com/schollz/croc.git synced 2024-11-23 23:54:17 +03:00

start over

This commit is contained in:
Zack Scholl 2018-06-28 05:09:11 -07:00
parent 606918b14e
commit 84f732b662
742 changed files with 165 additions and 302649 deletions

217
Gopkg.lock generated
View File

@ -1,217 +0,0 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
name = "github.com/briandowns/spinner"
packages = ["."]
revision = "5b875a9171af19dbde37e70a8fcbe2ebd7285e05"
version = "1.1"
[[projects]]
name = "github.com/cihub/seelog"
packages = ["."]
revision = "d2c6e5aa9fbfdd1c624e140287063c7730654115"
version = "v2.6"
[[projects]]
name = "github.com/davecgh/go-spew"
packages = ["spew"]
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
version = "v1.1.0"
[[projects]]
branch = "master"
name = "github.com/dustin/go-humanize"
packages = ["."]
revision = "02af3965c54e8cacf948b97fef38925c4120652c"
[[projects]]
name = "github.com/fatih/color"
packages = ["."]
revision = "5b77d2a35fb0ede96d138fc9a99f5c9b6aef11b4"
version = "v1.7.0"
[[projects]]
name = "github.com/fatih/structs"
packages = ["."]
revision = "a720dfa8df582c51dee1b36feabb906bde1588bd"
version = "v1.0"
[[projects]]
branch = "master"
name = "github.com/hashicorp/errwrap"
packages = ["."]
revision = "7554cd9344cec97297fa6649b055a8c98c2a1e55"
[[projects]]
branch = "master"
name = "github.com/hashicorp/go-multierror"
packages = ["."]
revision = "b7773ae218740a7be65057fc60b366a49b538a44"
[[projects]]
branch = "master"
name = "github.com/mars9/crypt"
packages = ["."]
revision = "65899cf653ff022fe5c7fe504b439feed9e7e0fc"
[[projects]]
name = "github.com/mattn/go-colorable"
packages = ["."]
revision = "167de6bfdfba052fa6b2d3664c8f5272e23c9072"
version = "v0.0.9"
[[projects]]
name = "github.com/mattn/go-isatty"
packages = ["."]
revision = "0360b2af4f38e8d38c7fce2a9f4e702702d73a39"
version = "v0.0.3"
[[projects]]
branch = "master"
name = "github.com/mitchellh/colorstring"
packages = ["."]
revision = "8631ce90f28644f54aeedcb3e389a85174e067d1"
[[projects]]
branch = "master"
name = "github.com/mitchellh/go-homedir"
packages = ["."]
revision = "3864e76763d94a6df2f9960b16a20a33da9f9a66"
[[projects]]
branch = "master"
name = "github.com/mr-tron/base58"
packages = ["base58"]
revision = "4df4dc6e86a912614d09719d10cad427b087cbfb"
[[projects]]
name = "github.com/pkg/errors"
packages = ["."]
revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
version = "v0.8.0"
[[projects]]
name = "github.com/pmezard/go-difflib"
packages = ["difflib"]
revision = "792786c7400a136282c1664665ae0a8db921c6c2"
version = "v1.0.0"
[[projects]]
branch = "master"
name = "github.com/schollz/bytetoword"
packages = ["."]
revision = "a75e6c9cd0e1fe6444905174c32d91aec9ce14f8"
[[projects]]
name = "github.com/schollz/mnemonicode"
packages = ["."]
revision = "15c9654387fad6d257aa28f9be57b9f124101955"
version = "v1.0.0"
[[projects]]
name = "github.com/schollz/peerdiscovery"
packages = ["."]
revision = "2c99137c9f8ff3597c2325c3be232612867652e3"
version = "v1.2.0"
[[projects]]
branch = "master"
name = "github.com/schollz/progressbar"
packages = ["."]
revision = "2283967e9a5af7a9eaddaaacb9f569314977fd03"
[[projects]]
branch = "master"
name = "github.com/schollz/tarinator-go"
packages = ["."]
revision = "0eab2c92d3ce5cb04535445b0825b2120c4acf3f"
[[projects]]
name = "github.com/sirupsen/logrus"
packages = ["."]
revision = "c155da19408a8799da419ed3eeb0cb5db0ad5dbc"
version = "v1.0.5"
[[projects]]
name = "github.com/stretchr/testify"
packages = ["assert"]
revision = "f35b8ab0b5a2cef36673838d662e249dd9c94686"
version = "v1.2.2"
[[projects]]
name = "github.com/urfave/cli"
packages = ["."]
revision = "cfb38830724cc34fedffe9a2a29fb54fa9169cd1"
version = "v1.20.0"
[[projects]]
name = "github.com/yudai/gotty"
packages = ["pkg/homedir"]
revision = "a080c85cbc59226c94c6941ad8c395232d72d517"
version = "v2.0.0-alpha.3"
[[projects]]
branch = "master"
name = "github.com/yudai/hcl"
packages = [
".",
"hcl",
"json"
]
revision = "5fa2393b3552119bf33a69adb1402a1160cba23d"
[[projects]]
branch = "master"
name = "golang.org/x/crypto"
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 = "afe8f62b1d6bbd81f31868121a50b06d8188e1f9"
[[projects]]
branch = "master"
name = "golang.org/x/sys"
packages = [
"unix",
"windows"
]
revision = "c4afb3effaa53fd9a06ca61262dc7ce8df4c081b"
[[projects]]
name = "golang.org/x/text"
packages = ["transform"]
revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0"
version = "v0.3.0"
[[projects]]
name = "gopkg.in/yaml.v2"
packages = ["."]
revision = "5420a8b6744d3b0345ab293f6fcba19c978f1183"
version = "v2.2.1"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "a89b8e32b8dbb85f236a17be149ba201f551f415791d1d6b95b203853fa7957c"
solver-name = "gps-cdcl"
solver-version = 1

View File

@ -1,90 +0,0 @@
# Gopkg.toml example
#
# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
# name = "github.com/user/project"
# version = "1.0.0"
#
# [[constraint]]
# name = "github.com/user/project2"
# branch = "dev"
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"
#
# [prune]
# non-go = false
# go-tests = true
# unused-packages = true
[[constraint]]
name = "github.com/cihub/seelog"
version = "2.6.0"
[[constraint]]
branch = "master"
name = "github.com/dustin/go-humanize"
[[constraint]]
name = "github.com/fatih/structs"
version = "1.0.0"
[[constraint]]
branch = "master"
name = "github.com/mars9/crypt"
[[constraint]]
name = "github.com/pkg/errors"
version = "0.8.0"
[[constraint]]
name = "github.com/schollz/mnemonicode"
version = "1.0.0"
[[constraint]]
name = "github.com/schollz/peerdiscovery"
branch = "master"
[[constraint]]
name = "github.com/schollz/progressbar"
branch = "master"
[[constraint]]
name = "github.com/schollz/tarinator-go"
branch = "master"
[[constraint]]
name = "github.com/sirupsen/logrus"
version = "1.0.5"
[[constraint]]
name = "github.com/urfave/cli"
version = "1.20.0"
[[constraint]]
name = "github.com/yudai/gotty"
version = "2.0.0-alpha.3"
[[constraint]]
branch = "master"
name = "github.com/yudai/hcl"
[[constraint]]
branch = "master"
name = "golang.org/x/crypto"
[[constraint]]
name = "gopkg.in/yaml.v2"
version = "2.2.1"
[prune]
go-tests = true
unused-packages = true

275
README.md
View File

@ -1,137 +1,190 @@
<p align="center">
<img
src="https://user-images.githubusercontent.com/6550035/31846899-2b8a7034-b5cf-11e7-9643-afe552226c59.png"
width="100%" border="0" alt="croc">
<br>
<a href="https://github.com/schollz/croc/releases/latest"><img src="https://img.shields.io/badge/version-2.2.0-brightgreen.svg?style=flat-square" alt="Version"></a>
<a href="https://saythanks.io/to/schollz"><img src="https://img.shields.io/badge/Say%20Thanks-!-yellow.svg?style=flat-square" alt="Go Report Card"></a>
</p>
https://medium.com/@simplyianm/why-gos-structs-are-superior-to-class-based-inheritance-b661ba897c67
<p align="center">Easily and securely transfer stuff from one computer to another.</p>
## Protocol
*croc* allows any two computers to directly and securely transfer files and folders. When sending a file, *croc* generates a random code phrase which must be shared with the recipient so they can receive the file. The code phrase encrypts all data and metadata and also serves to authorize the connection between the two computers in a intermediary relay. The relay connects the TCP ports between the two computers and does not store any information (and all information passing through it is encrypted).
Every GET/POST request should check the IP address and make sure that there are never more than 2 IP addresses using a single channel. Once two IP addresses are in, then the channel is *full*.
**New version released June 24th, 2018 - please upgrade if you are using the public relay.**
1. **Sender** requests new channel and receives empty channel from **Relay**, or obtains the channel they request (or an error if it is already occupied).
I hear you asking, *Why another open-source peer-to-peer file transfer utilities?* [There](https://github.com/cowbell/sharedrop) [are](https://github.com/webtorrent/instant.io) [great](https://github.com/kern/filepizza) [tools](https://github.com/warner/magic-wormhole) [that](https://github.com/zerotier/toss) [already](https://github.com/ipfs/go-ipfs) [do](https://github.com/zerotier/toss) [this](https://github.com/nils-werner/zget). But, after review, [I found it was useful to make another](https://schollz.github.io/sending-a-file/). Namely, *croc* has no dependencies (just [download a binary and run](https://github.com/schollz/croc/releases/latest)), it works on any operating system, and its blazingly fast because it does parallel transfer over multiple TCP ports.
POST /open
(optional)
{
"channel": "...", // optional
"role": "sender",
"success": true,
"message": "got channel"
}
Returns:
{
"channel": "...",
"uuid": "...",
"success": true,
"message": "got channel"
}
# Example
2. **Sender** generates *X* using PAKE from secret *pw*.
_These two gifs should run in sync if you force-reload (Ctl+F5)_
3. **Sender** sends *X* to **Relay** and the type of curve being used. Returns error if channel is already occupied by sender, otherwise it uses it.
**Sender:**
![send](https://raw.githubusercontent.com/schollz/croc/master/logo/sender2.gif)
**Receiver:**
![receive](https://raw.githubusercontent.com/schollz/croc/master/logo/receiver2.gif)
POST /channel/:channel
{
"uuid": "...",
"x": "...",
"curve": "p557"
}
Returns:
{
"success": true,
"message": "updated x, curve"
}
**Sender:**
4. **Sender** communicates channel + secret *pw* to **Recipient** (human interaction).
```
$ croc -send some-file-or-folder
Sending 4.4 MB file named 'some-file-or-folder'
Code is: cement-galaxy-alpha
5. **Recipient** connects to channel and receives UUID.
Your public key: ecad-bakery-cup-unlit-roam-fetid-arulo-updike
Recipient public key: bike-cokery-casina-donut-field-farrow-mega-shine
ok? (y/n): y
Sending (->[1]63982)..
89% |███████████████████████████████████ | [12s:1s]
File sent (2.6 MB/s)
```
**Receiver:**
```
$ croc
Enter receive code: cement-galaxy-alpha
Receiving file (4.4 MB) into: some-file-or-folder
Your public key: bike-cokery-casina-donut-field-farrow-mega-shine
Recipient public key: ecad-bakery-cup-unlit-roam-fetid-arulo-updike
ok? (y/n): y
Receiving (<-[1]63975)..
97% |██████████████████████████████████████ | [13s:0s]
Received file written to some-file-or-folder (2.6 MB/s)
```
Note, by default, you don't need any arguments for receiving! This makes it possible for you to just double click the executable to run (nice for those of us that aren't computer wizards).
## Using *croc* in pipes
You can easily use *croc* in pipes when you need to send data through stdin or get data from stdout.
**Sender:**
```
$ cat some_file_or_folder | croc
```
In this case *croc* will automatically use the stdin data and send and assign a filename like "croc-stdin-123456789".
**Receiver:**
```
$ croc --code code-phrase --yes --stdout | more
```
Here the reciever specified the code (`--code`) so it will not be prompted, and also specified `--yes` so the file will be automatically accepted. The output goes to stdout when flagged with `--stdout`.
5. **Recipient** requests *X* from **Relay** using the channel. Returns error if it doesn't exist yet.
# Install
GET /channel/:channel
Returns:
{
... all information
"success": true,
"message": "updated x"
}
[Download the latest release for your system](https://github.com/schollz/croc/releases/latest).
6. **Recipient** generates *Y*, session key *k_B*, and hashed session key *H(k_B)* using PAKE from secret *pw*.
Or, you can [install Go](https://golang.org/dl/) and build from source with `go get github.com/schollz/croc`.
7. **Recipient** sends *Y*, *H(H(k_B))* to **Relay**.
```
POST /channel/:channel
{
"uuid": "...",
"y": "...",
"hh_k": "..."
}
Returns:
{
"success": true,
"message": "updated y"
}
```
7. **Sender** requests *Y*, *H(H(k_B))* from **Relay**.
```
GET /sender/:channel/y
Returns:
{
"y": "...",
"hh_k": "...",
"success": true,
"message": "got y"
}
```
8. **Sender** uses *Y* to generate its session key *k_A* and *H(k_A)*, and checks *H(H(k_A))*==*H(H(k_B))*. **Sender** aborts here if it is incorrect.
9. **Sender** gives the **Relay** authentication *H(k_A)*.
```
POST /sender/:channel/h_k
{
"h_k": "..."
}
Returns:
{
"success": true,
"message": "updated h_k"
}
```
10. **Recipient** requests *H(k_A)* from relay and checks against its own. If it doesn't match, then bail.
```
GET /recipient/:channel/h_k
Returns:
{
"h_k": "...",
"success": true,
"message": "got h_k"
}
```
11. **Sender** requests that **Relay** creates open TCP connection with itself as sender, identified by *H(k_A)*.
```
GET /sender/:channel/open
Returns:
{
"success": true,
"message": "opened channel"
}
```
12. **Sender** encrypts data with *k*.
13. **Recipient** requests that **Relay** creates open TCP connection with itself as recipient, identified by *H(k_B)*.
```
GET /recipient/:channel/open
Returns:
{
"success": true,
"message": "opened channel"
}
```
this will save the IP address as the reciever
14. **Recipient** starts listening to Relay. (Relay accepts **Recipient** because it knows **Recipient**'s IP address).
15. **Relay**, when it has a sender and recipient identified for TCP connections, staples the connections together.
16. **Sender** asks **Relay** whether the recipient is ready and connections are stapled.
```
GET /sender/:channel/isready
Returns:
{
"ready": true,
"success": true,
"message": "is ready"
}
```
17. **Sender** sends data over TCP.
18. **Recipient** closes relay when finished. Anyone participating in the channel can close the relay at any time. Any of the routes except the first ones will return errors if stuff doesn't exist.
```
GET /close/:channel
Returns:
{
"success": true,
"message": "closed"
}
```
# How does it work?
*croc* is similar to [magic-wormhole](https://github.com/warner/magic-wormhole#design) in spirit. Like *magic-wormhole*, *croc* generates a code phrase for you to share with your friend which allows secure end-to-end transferring of files and folders through a intermediary relay that connects the TCP ports between the two computers. The standard relay is on a public IP address (default `cowyo.com`), but before transmitting the file the two instances of *croc* send out UDP broadcasts to determine if they are both on the local network, and use a local relay instead of the cloud relay in the case that they are both local.
The code phrase allows the relay to match the receiver and the sender. Once matched, the relay shares the public keys so that the sender and recipient can further authenticate whether or not they have the right person. Once both sides verify and consent to the transfer, then the sender will encrypt the data using the recipient's public key, so that only they can decrypt the data. After a successful transfer, the public key is stored and next time it is not prompted and automatically trusted (TOFU).
# Notes
The transfer uses [Go channels](https://golang.org/doc/effective_go.html?h=chan#channels) and parallel connections to pipe all the data. After the transmission the channels are destroyed and all the connection and meta data information is wiped from the relay server. The encrypted file data never is stored on the relay.
https://play.golang.org/p/1_dfm6us8Nx
**Security**
https://git.tws.website/t/thesis
The first time you use croc you will generate a unique NaCl box keypair (which uses Curve25519, XSalsa20 and Poly1305) that is unique to your computer. This keypair is used to transfer the encryption key to the recipient, and guarantees that only the recipient can decrypt the encryption key to decrypt the file data. The encryption key is a cryptographically generated random 20 characters. The file data is encrypted using the encryption key with AES-256. This method guarantees that all the file data going over the wire is secure, and that the only person who can decrypt it is the recipient (i.e. a MITM attacker cannot decrypt it without the keypair on the recipient's computer).
https://github.com/tscholl2/siec
The keypair also serves as a second method of authentication. After both the sender and recipient enter their code phrases, they will be able to see each other's public keys. If the public key of the other person does not match what they say it should be (i.e. an attacker is trying to use the same code phrase to get your file - possible though unlikely), then you can cancel the transfer.
*croc* as a library
On the receiver's computer, each piece of received encrypted data is written to a separate file. These files are concatenated and then decrypted. The hash of the decrypted file is then checked against the hash transmitted from the sender (part of the meta data block).
- use functional options
- every GET/POST request should check the IP address and make sure that there are never more than 2 IP addresses using a single channel
## Run your own relay
*croc* relies on a TCP relay to staple the parallel incoming and outgoing connections. The relay temporarily stores connection information and the encrypted meta information. The default uses a public relay at, `cowyo.com`, which has a 30-day uptime of 99.989% ([click here to check the current status of the public relay](https://stats.uptimerobot.com/lOwJYIgRm)).
You can also run your own relay, it is very easy. On your server, `your-server.com`, just run
```
$ croc -relay
```
Now, when you use *croc* to send and receive you should add `-server your-server.com` to use your relay server. Make sure to open up TCP ports 27001-27009.
# Contribute
I am awed by all the [great contributions](#acknowledgements) made! If you feel like contributing, in any way, by all means you can send an Issue, a PR, ask a question, or tweet me ([@yakczar](http://ctt.ec/Rq054)).
# License
MIT
# Acknowledgements
Thanks...
- ...[@warner](https://github.com/warner) for the [idea](https://github.com/warner/magic-wormhole).
- ...[@tscholl2](https://github.com/tscholl2) for the [encryption gists](https://gist.github.com/tscholl2/dc7dc15dc132ea70a98e8542fefffa28).
- ...[@skorokithakis](https://github.com/skorokithakis) for [code on proxying two connections](https://www.stavros.io/posts/proxying-two-connections-go/).
- ...for making pull requests [@Girbons](https://github.com/Girbons), [@techtide](https://github.com/techtide), [@heymatthew](https://github.com/heymatthew), [@Lunsford94](https://github.com/Lunsford94), [@lummie](https://github.com/lummie), [@jesuiscamille](https://github.com/jesuiscamille), [@threefjord](https://github.com/threefjord), [@marcossegovia](https://github.com/marcossegovia), [@csleong98](https://github.com/csleong98), [@afotescu](https://github.com/afotescu), [@callmefever](https://github.com/callmefever), [@El-JojA](https://github.com/El-JojA), [@anatolyyyyyy](https://github.com/anatolyyyyyy), [@goggle](https://github.com/goggle), [@smileboywtu](https://github.com/smileboywtu)!
croc.New()
croc.SetX().... Set parameters
croc.Send(file)
croc.Receive()

1012
connect.go

File diff suppressed because it is too large Load Diff

125
crypto.go
View File

@ -1,125 +0,0 @@
package main
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/sha1"
"crypto/sha256"
"encoding/binary"
"encoding/hex"
"fmt"
mathrand "math/rand"
"os"
"strings"
"time"
"github.com/mars9/crypt"
"github.com/schollz/mnemonicode"
log "github.com/sirupsen/logrus"
"golang.org/x/crypto/pbkdf2"
)
func init() {
mathrand.Seed(time.Now().UTC().UnixNano())
}
func GetRandomName() string {
result := []string{}
bs := make([]byte, 4)
binary.LittleEndian.PutUint32(bs, mathrand.Uint32())
result = mnemonicode.EncodeWordList(result, bs)
return strings.Join(result, "-")
}
func Encrypt(plaintext []byte, passphrase string, dontencrypt ...bool) (encrypted []byte, salt string, iv string) {
if len(dontencrypt) > 0 && dontencrypt[0] {
return plaintext, "salt", "iv"
}
key, saltBytes := deriveKey(passphrase, nil)
ivBytes := make([]byte, 12)
// http://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf
// Section 8.2
rand.Read(ivBytes)
b, _ := aes.NewCipher(key)
aesgcm, _ := cipher.NewGCM(b)
encrypted = aesgcm.Seal(nil, ivBytes, plaintext, nil)
salt = hex.EncodeToString(saltBytes)
iv = hex.EncodeToString(ivBytes)
return
}
func Decrypt(data []byte, passphrase string, salt string, iv string, dontencrypt ...bool) (plaintext []byte, err error) {
if len(dontencrypt) > 0 && dontencrypt[0] {
return data, nil
}
saltBytes, _ := hex.DecodeString(salt)
ivBytes, _ := hex.DecodeString(iv)
key, _ := deriveKey(passphrase, saltBytes)
b, _ := aes.NewCipher(key)
aesgcm, _ := cipher.NewGCM(b)
plaintext, err = aesgcm.Open(nil, ivBytes, data, nil)
return
}
func deriveKey(passphrase string, salt []byte) ([]byte, []byte) {
if salt == nil {
salt = make([]byte, 8)
// http://www.ietf.org/rfc/rfc2898.txt
// Salt.
rand.Read(salt)
}
return pbkdf2.Key([]byte(passphrase), salt, 1000, 32, sha256.New), salt
}
func Hash(data string) string {
return HashBytes([]byte(data))
}
func HashBytes(data []byte) string {
sum := sha256.Sum256(data)
return fmt.Sprintf("%x", sum)
}
func EncryptFile(inputFilename string, outputFilename string, password string) error {
return cryptFile(inputFilename, outputFilename, password, true)
}
func DecryptFile(inputFilename string, outputFilename string, password string) error {
return cryptFile(inputFilename, outputFilename, password, false)
}
func cryptFile(inputFilename string, outputFilename string, password string, encrypt bool) error {
in, err := os.Open(inputFilename)
if err != nil {
return err
}
defer in.Close()
out, err := os.Create(outputFilename)
if err != nil {
return err
}
defer func() {
if err := out.Sync(); err != nil {
log.Error(err)
}
if err := out.Close(); err != nil {
log.Error(err)
}
}()
c := &crypt.Crypter{
HashFunc: sha1.New,
HashSize: sha1.Size,
Key: crypt.NewPbkdf2Key([]byte(password), 32),
}
if encrypt {
if err := c.Encrypt(out, in); err != nil {
return err
}
} else {
if err := c.Decrypt(out, in); err != nil {
return err
}
}
return nil
}

View File

@ -1,49 +0,0 @@
package main
import (
"io/ioutil"
"os"
"testing"
)
func TestEncrypt(t *testing.T) {
key := GetRandomName()
encrypted, salt, iv := Encrypt([]byte("hello, world"), key)
decrypted, err := Decrypt(encrypted, key, salt, iv)
if err != nil {
t.Error(err)
}
if string(decrypted) != "hello, world" {
t.Error("problem decrypting")
}
_, err = Decrypt(encrypted, "wrong passphrase", salt, iv)
if err == nil {
t.Error("should not work!")
}
}
func TestEncryptFiles(t *testing.T) {
key := GetRandomName()
if err := ioutil.WriteFile("temp", []byte("hello, world!"), 0644); err != nil {
t.Error(err)
}
if err := EncryptFile("temp", "temp.enc", key); err != nil {
t.Error(err)
}
if err := DecryptFile("temp.enc", "temp.dec", key); err != nil {
t.Error(err)
}
data, err := ioutil.ReadFile("temp.dec")
if string(data) != "hello, world!" {
t.Errorf("Got something weird: " + string(data))
}
if err != nil {
t.Error(err)
}
if err := DecryptFile("temp.enc", "temp.dec", key+"wrong password"); err == nil {
t.Error("should throw error!")
}
os.Remove("temp.dec")
os.Remove("temp.enc")
os.Remove("temp")
}

View File

@ -1,34 +0,0 @@
# .goreleaser.yml
# Build customization
builds:
- binary: croc
goos:
- windows
- darwin
- linux
goarch:
- amd64
- arm
- 386
goarm:
- 6
ignore:
- goos: darwin
goarch: arm
- goos: windows
goarch: arm
- goos: darwin
goarch: 386
env:
- CGO_ENABLED=0
archive:
replacements:
amd64: 64bit
386: 32bit
darwin: OSX
linux_arm: raspberry_pi
format_overrides:
- goos: windows
format: zip
- goos: darwin
format: zip

View File

@ -1,146 +0,0 @@
package keypair
import (
crypto_rand "crypto/rand"
"encoding/json"
"errors"
"io"
"github.com/mr-tron/base58/base58"
"golang.org/x/crypto/nacl/box"
)
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)
}
// Load will load from a string
func Load(keypairString string) (kp KeyPair, err error) {
err = json.Unmarshal([]byte(keypairString), &kp)
if err != nil {
return
}
kp, err = New(kp)
return
}
// 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
}
publicKey = base58.FastBase58Encoding(publicKeyBytes[:])
privateKey = base58.FastBase58Encoding(privateKeyBytes[:])
return
}
func keyToBytes(s string) (key *[32]byte, err error) {
var keyBytes []byte
keyBytes, err = base58.FastBase58Decoding(s)
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
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

380
main.go
View File

@ -1,380 +0,0 @@
package main
import (
"bufio"
"fmt"
"io/ioutil"
"os"
"reflect"
"strconv"
"strings"
log "github.com/cihub/seelog"
"github.com/fatih/structs"
"github.com/urfave/cli"
"github.com/yudai/gotty/pkg/homedir"
"github.com/yudai/hcl"
yaml "gopkg.in/yaml.v2"
)
const BUFFERSIZE = 1024
type AppConfig struct {
Relay bool `yaml:"relay" flagName:"relay" flagSName:"r" flagDescribe:"Run as relay" default:"false"`
Debug bool `yaml:"debug" flagName:"debug" flagSName:"d" flagDescribe:"Debug mode" default:"false"`
Wait bool `yaml:"wait" flagName:"wait" flagSName:"w" flagDescribe:"Wait for code to be sent" default:"false"`
PathSpec bool `yaml:"ask-save" flagName:"ask-save" flagSName:"q" flagDescribe:"Ask for path to save to" default:"false"`
DontEncrypt bool `yaml:"no-encrypt" flagName:"no-encrypt" flagSName:"g" flagDescribe:"Turn off encryption" default:"false"`
UseStdout bool `yaml:"stdout" flagName:"stdout" flagSName:"o" flagDescribe:"Use stdout" default:"false"`
Yes bool `yaml:"yes" flagName:"yes" flagSName:"y" flagDescribe:"Automatically accept file" default:"false"`
Local bool `yaml:"local" flagName:"local" flagSName:"lo" flagDescribe:"Use local relay when sending" default:"false"`
NoLocal bool `yaml:"no-local" flagName:"no-local" flagSName:"nlo" flagDescribe:"Don't create local relay" default:"false"`
Server string `yaml:"server" flagName:"server" flagSName:"l" flagDescribe:"Croc relay to use" default:"cowyo.com"`
File string `yaml:"send" flagName:"send" flagSName:"s" flagDescribe:"File to send default:""`
Path string `yaml:"save" flagName:"save" flagSName:"p" flagDescribe:"Path to save to" default:""`
Code string `yaml:"code" flagName:"code" flagSName:"c" flagDescribe:"Use your own code phrase" default:""`
Rate int `yaml:"rate" flagName:"rate" flagSName:"R" flagDescribe:"Throttle down to speed in kbps" default:"1000000"`
NumberOfConnections int `yaml:"threads" flagName:"threads" flagSName:"n" flagDescribe:"Number of threads to use" default:"4"`
}
var email string
var author string
var version string
func init() {
SetLogLevel("debug")
}
func main() {
defer log.Flush()
app := cli.NewApp()
app.Name = "croc"
app.Version = version
app.Author = author
app.Email = email
app.Usage = "send file by croc bridge"
app.HideHelp = true
cli.AppHelpTemplate = helpTemplate
appOptions := &AppConfig{}
if err := ApplyDefaultValues(appOptions); err != nil {
exit(err, 1)
}
cliFlags, flagMappings, err := GenerateFlags(appOptions)
if err != nil {
exit(err, 3)
}
app.Flags = append(
cliFlags,
cli.StringFlag{
Name: "config",
Value: "~/.croc",
Usage: "Config file path",
EnvVar: "CROC_CONFIG",
},
)
app.Action = func(c *cli.Context) {
configFile := c.String("config")
_, err := os.Stat(homedir.Expand(configFile))
if configFile != "~/.croc" || !os.IsNotExist(err) {
if err := ApplyConfigFileYaml(configFile, appOptions); err != nil {
exit(err, 2)
}
}
ApplyFlags(cliFlags, flagMappings, c, appOptions)
if appOptions.Relay {
fmt.Println("running relay on local address " + GetLocalIP())
r := NewRelay(appOptions)
r.Run()
} else {
c, err := NewConnection(appOptions)
if err != nil {
fmt.Printf("Error! Please submit the following error to https://github.com/schollz/croc/issues:\n\n'NewConnection: %s'\n\n", err.Error())
return
}
err = c.Run()
if err != nil {
fmt.Printf("Error! Please submit the following error to https://github.com/schollz/croc/issues:\n\n'Run: %s'\n\n", err.Error())
}
}
}
app.Run(os.Args)
}
func getInput(prompt string) string {
reader := bufio.NewReader(os.Stdin)
fmt.Fprintf(os.Stderr, "%s", prompt)
text, _ := reader.ReadString('\n')
return strings.TrimSpace(text)
}
func exit(err error, code int) {
if err != nil {
fmt.Println(err)
}
os.Exit(code)
}
func ApplyDefaultValues(struct_ interface{}) (err error) {
o := structs.New(struct_)
for _, field := range o.Fields() {
defaultValue := field.Tag("default")
if defaultValue == "" {
continue
}
var val interface{}
switch field.Kind() {
case reflect.String:
val = defaultValue
case reflect.Bool:
if defaultValue == "true" {
val = true
} else if defaultValue == "false" {
val = false
} else {
return fmt.Errorf("invalid bool expression: %v, use true/false", defaultValue)
}
case reflect.Int:
val, err = strconv.Atoi(defaultValue)
if err != nil {
return err
}
default:
val = field.Value()
}
field.Set(val)
}
return nil
}
func GenerateFlags(options ...interface{}) (flags []cli.Flag, mappings map[string]string, err error) {
mappings = make(map[string]string)
for _, struct_ := range options {
o := structs.New(struct_)
for _, field := range o.Fields() {
flagName := field.Tag("flagName")
if flagName == "" {
continue
}
envName := "CROC_" + strings.ToUpper(strings.Join(strings.Split(flagName, "-"), "_"))
mappings[flagName] = field.Name()
flagShortName := field.Tag("flagSName")
if flagShortName != "" {
flagName += ", " + flagShortName
}
flagDescription := field.Tag("flagDescribe")
switch field.Kind() {
case reflect.String:
flags = append(flags, cli.StringFlag{
Name: flagName,
Value: field.Value().(string),
Usage: flagDescription,
EnvVar: envName,
})
case reflect.Bool:
flags = append(flags, cli.BoolFlag{
Name: flagName,
Usage: flagDescription,
EnvVar: envName,
})
case reflect.Int:
flags = append(flags, cli.IntFlag{
Name: flagName,
Value: field.Value().(int),
Usage: flagDescription,
EnvVar: envName,
})
}
}
}
return
}
func ApplyFlags(
flags []cli.Flag,
mappingHint map[string]string,
c *cli.Context,
options ...interface{},
) {
objects := make([]*structs.Struct, len(options))
for i, struct_ := range options {
objects[i] = structs.New(struct_)
}
for flagName, fieldName := range mappingHint {
if !c.IsSet(flagName) {
continue
}
var field *structs.Field
var ok bool
for _, o := range objects {
field, ok = o.FieldOk(fieldName)
if ok {
break
}
}
if field == nil {
continue
}
var val interface{}
switch field.Kind() {
case reflect.String:
val = c.String(flagName)
case reflect.Bool:
val = c.Bool(flagName)
case reflect.Int:
val = c.Int(flagName)
}
field.Set(val)
}
}
func ApplyConfigFile(filePath string, options ...interface{}) error {
filePath = homedir.Expand(filePath)
if _, err := os.Stat(filePath); os.IsNotExist(err) {
return err
}
fileString := []byte{}
log.Debugf("Loading config file at: %s", filePath)
fileString, err := ioutil.ReadFile(filePath)
if err != nil {
return err
}
for _, object := range options {
if err := hcl.Decode(object, string(fileString)); err != nil {
return err
}
}
return nil
}
func ApplyConfigFileYaml(filePath string, options ...interface{}) error {
filePath = homedir.Expand(filePath)
if _, err := os.Stat(filePath); os.IsNotExist(err) {
return err
}
fileString := []byte{}
log.Debugf("Loading config file at: %s", filePath)
fileString, err := ioutil.ReadFile(filePath)
if err != nil {
return err
}
for _, object := range options {
if err := yaml.Unmarshal(fileString, object); err != nil {
return err
}
}
return nil
}
func SaveConfigFileYaml(filePath string, options ...interface{}) error {
filePath = homedir.Expand(filePath)
if _, err := os.Stat(filePath); os.IsNotExist(err) {
return err
}
fd, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE, 0644)
if err != nil {
return err
}
defer fd.Close()
for _, object := range options {
if byteString, err := yaml.Marshal(object); err != nil {
return err
} else {
fd.Write(byteString)
}
}
return nil
}
var helpTemplate = `
,_
>' )
( ( \
|| \
/^^^^\ ||
/^^\________/0 \ ||
( ` + "`" + `~+++,,_||__,,++~^^^^^^^
...V^V^V^V^V^V^\...............................
NAME:
{{.Name}} - {{.Usage}}
USAGE:
{{.Name}} [options]
VERSION:
{{.Version}}{{if or .Author .Email}}
AUTHOR:{{if .Author}}
{{.Author}}{{if .Email}} - <{{.Email}}>{{end}}{{else}}
{{.Email}}{{end}}{{end}}
OPTIONS:
{{range .Flags}}{{.}}
{{end}}
`
// SetLogLevel determines the log level
func SetLogLevel(level string) (err error) {
// https://en.wikipedia.org/wiki/ANSI_escape_code#3/4_bit
// https://github.com/cihub/seelog/wiki/Log-levels
appConfig := `
<seelog minlevel="` + level + `">
<outputs formatid="stdout">
<filter levels="debug,trace">
<console formatid="debug"/>
</filter>
<filter levels="info">
<console formatid="info"/>
</filter>
<filter levels="critical,error">
<console formatid="error"/>
</filter>
<filter levels="warn">
<console formatid="warn"/>
</filter>
</outputs>
<formats>
<format id="stdout" format="%Date %Time [%LEVEL] %File %FuncShort:%Line %Msg %n" />
<format id="debug" format="%Date %Time %EscM(37)[%LEVEL]%EscM(0) %File %FuncShort:%Line %Msg %n" />
<format id="info" format="%EscM(36)[%LEVEL]%EscM(0) %Msg %n" />
<format id="warn" format="%Date %Time %EscM(33)[%LEVEL]%EscM(0) %File %FuncShort:%Line %Msg %n" />
<format id="error" format="%Date %Time %EscM(31)[%LEVEL]%EscM(0) %File %FuncShort:%Line %Msg %n" />
</formats>
</seelog>
`
logger, err := log.LoggerFromConfigAsBytes([]byte(appConfig))
if err != nil {
return
}
log.ReplaceLogger(logger)
return
}

View File

@ -1,66 +0,0 @@
package randomstring
import (
"crypto/rand"
"encoding/base64"
"fmt"
"io"
)
// Adapted from https://elithrar.github.io/article/generating-secure-random-numbers-crypto-rand/
func init() {
assertAvailablePRNG()
}
func assertAvailablePRNG() {
// Assert that a cryptographically secure PRNG is available.
// Panic otherwise.
buf := make([]byte, 1)
_, err := io.ReadFull(rand.Reader, buf)
if err != nil {
panic(fmt.Sprintf("crypto/rand is unavailable: Read() failed with %#v", err))
}
}
// GenerateRandomBytes returns securely generated random bytes.
// It will return an error if the system's secure random
// number generator fails to function correctly, in which
// case the caller should not continue.
func GenerateRandomBytes(n int) ([]byte, error) {
b := make([]byte, n)
_, err := rand.Read(b)
// Note that err == nil only if we read len(b) bytes.
if err != nil {
return nil, err
}
return b, nil
}
// GenerateRandomString returns a securely generated random string.
// It will return an error if the system's secure random
// number generator fails to function correctly, in which
// case the caller should not continue.
func GenerateRandomString(n int) (string, error) {
const letters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-"
bytes, err := GenerateRandomBytes(n)
if err != nil {
return "", err
}
for i, b := range bytes {
bytes[i] = letters[b%byte(len(letters))]
}
return string(bytes), nil
}
// GenerateRandomStringURLSafe returns a URL-safe, base64 encoded
// securely generated random string.
// It will return an error if the system's secure random
// number generator fails to function correctly, in which
// case the caller should not continue.
func GenerateRandomStringURLSafe(n int) (string, error) {
b, err := GenerateRandomBytes(n)
return base64.URLEncoding.EncodeToString(b), err
}

View File

@ -1,15 +0,0 @@
package randomstring
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
func TestRandomString(t *testing.T) {
r, err := GenerateRandomString(20)
assert.Nil(t, err)
assert.Equal(t, 20, len(r))
fmt.Println(r)
}

489
relay.go
View File

@ -1,489 +0,0 @@
package main
import (
"net"
"strconv"
"strings"
"sync"
"time"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
)
const MAX_NUMBER_THREADS = 8
const CONNECTION_TIMEOUT = time.Hour
type connectionMap struct {
receiver map[string]net.Conn
sender map[string]net.Conn
metadata map[string]string
potentialReceivers map[string]struct{}
rpublicKey map[string]string
spublicKey map[string]string
sconsent map[string]string
passphrase map[string]string
receiverReady map[string]bool
sync.RWMutex
}
func (c *connectionMap) IsSenderConnected(key string) (found bool) {
c.RLock()
defer c.RUnlock()
_, found = c.sender[key]
return
}
type Relay struct {
connections connectionMap
Debug bool
NumberOfConnections int
}
func NewRelay(config *AppConfig) *Relay {
r := &Relay{
Debug: config.Debug,
NumberOfConnections: MAX_NUMBER_THREADS,
}
log.SetFormatter(&log.TextFormatter{})
if r.Debug {
log.SetLevel(log.DebugLevel)
} else {
log.SetLevel(log.WarnLevel)
}
return r
}
func (r *Relay) Run() {
r.connections = connectionMap{}
r.connections.Lock()
r.connections.receiver = make(map[string]net.Conn)
r.connections.sender = make(map[string]net.Conn)
r.connections.metadata = make(map[string]string)
r.connections.spublicKey = make(map[string]string)
r.connections.rpublicKey = make(map[string]string)
r.connections.passphrase = make(map[string]string)
r.connections.sconsent = make(map[string]string)
r.connections.potentialReceivers = make(map[string]struct{})
r.connections.receiverReady = make(map[string]bool)
r.connections.Unlock()
r.runServer()
}
func (r *Relay) runServer() {
logger := log.WithFields(log.Fields{
"function": "main",
})
logger.Debug("Initializing")
var wg sync.WaitGroup
wg.Add(r.NumberOfConnections)
for id := 0; id < r.NumberOfConnections; id++ {
go r.listenerThread(id, &wg)
}
wg.Wait()
}
func (r *Relay) listenerThread(id int, wg *sync.WaitGroup) {
defer wg.Done()
if err := r.listener(id); err != nil {
return
}
}
func (r *Relay) listener(id int) (err error) {
port := strconv.Itoa(27001 + id)
logger := log.WithFields(log.Fields{
"function": "listener:" + port,
})
server, err := net.Listen("tcp", "0.0.0.0:"+port)
if err != nil {
return errors.Wrap(err, "Error listening on :"+port)
}
defer server.Close()
logger.Debug("waiting for connections")
//Spawn a new goroutine whenever a client connects
for {
connection, err := server.Accept()
if err != nil {
return errors.Wrap(err, "problem accepting connection")
}
logger.Debugf("Client %s connected", connection.RemoteAddr().String())
go r.clientCommuncation(id, connection)
}
}
func (r *Relay) clientCommuncation(id int, connection net.Conn) {
logger := log.WithFields(log.Fields{
"id": id,
"ip": connection.RemoteAddr().String(),
})
sendMessage("who?", connection)
m := strings.Split(receiveMessage(connection), ".")
if len(m) < 4 {
logger.Debug("exiting, not enough information")
sendMessage("not enough information", connection)
return
}
connectionType, publicKey, codePhrase, metaData := m[0], m[1], m[2], m[3]
logger.Debugf("got connection from %s", publicKey)
key := codePhrase + "-" + strconv.Itoa(id)
switch connectionType {
case "s": // sender connection
startTime := time.Now()
deleteAll := func() {
r.connections.Lock()
// close connections
if _, ok := r.connections.sender[key]; ok {
r.connections.sender[key].Close()
}
if _, ok := r.connections.receiver[key]; ok {
r.connections.receiver[key].Close()
}
// delete connctions
delete(r.connections.sender, key)
delete(r.connections.receiver, key)
delete(r.connections.metadata, key)
delete(r.connections.potentialReceivers, key)
delete(r.connections.spublicKey, key)
delete(r.connections.rpublicKey, key)
delete(r.connections.receiverReady, key)
delete(r.connections.passphrase, key)
r.connections.Unlock()
logger.Debug("deleted sender and receiver")
}
defer deleteAll()
if r.connections.IsSenderConnected(key) {
sendMessage("no", connection)
return
}
r.connections.Lock()
r.connections.metadata[key] = metaData
r.connections.sender[key] = connection
r.connections.spublicKey[key] = publicKey
r.connections.Unlock()
// wait for receiver
receiversAddress := ""
receiversPublicKey := ""
isTimeout := time.Duration(0)
log.Debug("waiting for reciever for sender")
for {
if CONNECTION_TIMEOUT <= isTimeout {
sendMessage("timeout", connection)
return
}
r.connections.RLock()
if _, ok := r.connections.receiver[key]; ok {
receiversAddress = r.connections.receiver[key].RemoteAddr().String()
}
if _, ok := r.connections.rpublicKey[key]; ok {
receiversPublicKey = r.connections.rpublicKey[key]
}
r.connections.RUnlock()
if receiversAddress != "" && receiversPublicKey != "" {
break
}
time.Sleep(100 * time.Millisecond)
isTimeout += 100 * time.Millisecond
}
logger.Debug("telling sender ok")
sendMessage(receiversAddress, connection)
sendMessage(receiversPublicKey, connection)
// TODO ASK FOR OKAY HERE TOO
sconsent := receiveMessage(connection)
r.connections.Lock()
r.connections.sconsent[key] = sconsent
r.connections.Unlock()
logger.Debugf("got consent: %+v", sconsent)
if sconsent != "ok" {
return
}
logger.Debug("waiting for encrypted passphrase")
encryptedPassphrase := receiveMessage(connection)
r.connections.Lock()
r.connections.passphrase[key] = encryptedPassphrase
r.connections.Unlock()
// wait for receiver ready
startTime = time.Now()
for {
r.connections.RLock()
if _, ok := r.connections.receiverReady[key]; ok {
r.connections.RUnlock()
break
}
r.connections.RUnlock()
if time.Since(startTime) > 5*time.Minute {
return
}
}
// go reciever ready tell sender to go
sendMessage("go", connection)
logger.Debug("preparing pipe")
r.connections.Lock()
con1 := r.connections.sender[key]
con2 := r.connections.receiver[key]
r.connections.Unlock()
logger.Debug("piping connections")
Pipe(con1, con2)
logger.Debug("done piping")
case "r", "c": // receiver
startTime := time.Now()
log.Debug("is receiver")
r.connections.RLock()
_, foundReceiver := r.connections.potentialReceivers[key]
r.connections.RUnlock()
if foundReceiver {
log.Debug("already have receiver")
sendMessage("no", connection)
return
}
// add as a potential receiver
logger.Debug("adding as potential reciever")
r.connections.Lock()
r.connections.potentialReceivers[key] = struct{}{}
r.connections.rpublicKey[key] = publicKey
r.connections.receiver[key] = connection
r.connections.Unlock()
// wait for sender's metadata
sendersAddress := ""
sendersPublicKey := ""
startTime = time.Now()
for {
r.connections.RLock()
// check if been deleted
if _, ok := r.connections.potentialReceivers[key]; !ok {
log.Debug("deleting and finishing")
r.connections.RUnlock()
return
}
if _, ok := r.connections.metadata[key]; ok {
if _, ok2 := r.connections.sender[key]; ok2 {
sendersAddress = r.connections.sender[key].RemoteAddr().String()
logger.Debug("got sender meta data")
}
}
if _, ok := r.connections.spublicKey[key]; ok {
sendersPublicKey = r.connections.spublicKey[key]
logger.Debugf("got sender public key: %s", sendersPublicKey)
}
r.connections.RUnlock()
if sendersAddress != "" && sendersPublicKey != "" {
break
}
if connectionType == "c" {
sendMessage("0-0-0-0.0.0.0", connection)
// sender is not ready so delete connection
r.connections.Lock()
delete(r.connections.potentialReceivers, key)
r.connections.Unlock()
return
}
time.Sleep(100 * time.Millisecond)
if time.Since(startTime) > 5*time.Minute {
return
}
}
// send meta data
r.connections.RLock()
sendMessage(r.connections.metadata[key]+"-"+sendersAddress, connection)
sendMessage(sendersPublicKey, connection)
r.connections.RUnlock()
// check for senders consent
sendersConsent := ""
startTime = time.Now()
for {
r.connections.RLock()
// check if been deleted
if _, ok := r.connections.potentialReceivers[key]; !ok {
log.Debug("deleting and finishing")
r.connections.RUnlock()
return
}
if _, ok := r.connections.sconsent[key]; ok {
sendersConsent = r.connections.sconsent[key]
}
r.connections.RUnlock()
if sendersConsent != "" {
break
}
time.Sleep(100 * time.Millisecond)
if time.Since(startTime) > 5*time.Minute {
return
}
}
if sendersConsent != "ok" {
// TODO: delete everything
return
}
// now get passphrase
sendersPassphrase := ""
startTime = time.Now()
for {
r.connections.RLock()
// check if been deleted
if _, ok := r.connections.potentialReceivers[key]; !ok {
log.Debug("deleting and finishing")
r.connections.RUnlock()
return
}
if _, ok := r.connections.passphrase[key]; ok {
sendersPassphrase = r.connections.passphrase[key]
logger.Debugf("got sender passphrase: %s", sendersPassphrase)
}
r.connections.RUnlock()
if sendersPassphrase != "" {
break
}
if time.Since(startTime) > 5*time.Minute {
return
}
time.Sleep(100 * time.Millisecond)
}
// check for receiver's consent
consent := receiveMessage(connection)
logger.Debugf("consent: %s", consent)
if consent == "ok" {
logger.Debug("got consent")
// wait for encrypted passphrase
encryptedPassphrase := ""
startTime = time.Now()
for {
r.connections.RLock()
// check if been deleted
if _, ok := r.connections.potentialReceivers[key]; !ok {
log.Debug("deleting and finishing")
r.connections.RUnlock()
return
}
if _, ok := r.connections.passphrase[key]; ok {
encryptedPassphrase = r.connections.passphrase[key]
logger.Debugf("got passphrase: %s", r.connections.passphrase[key])
}
r.connections.RUnlock()
if encryptedPassphrase != "" {
break
}
if time.Since(startTime) > 5*time.Minute {
return
}
time.Sleep(100 * time.Millisecond)
}
sendMessage(encryptedPassphrase, connection)
}
receiveMessage(connection)
time.Sleep(10 * time.Millisecond)
r.connections.Lock()
r.connections.receiverReady[key] = true
r.connections.Unlock()
default:
logger.Debugf("Got unknown protocol: '%s'", connectionType)
}
}
func sendMessage(message string, connection net.Conn) {
message = fillString(message, BUFFERSIZE)
connection.Write([]byte(message))
}
func receiveMessage(connection net.Conn) string {
logger := log.WithFields(log.Fields{
"func": "receiveMessage",
"ip": connection.RemoteAddr().String(),
})
messageByte := make([]byte, BUFFERSIZE)
err := connection.SetReadDeadline(time.Now().Add(60 * time.Minute))
if err != nil {
logger.Warn(err)
}
err = connection.SetDeadline(time.Now().Add(60 * time.Minute))
if err != nil {
logger.Warn(err)
}
err = connection.SetWriteDeadline(time.Now().Add(60 * time.Minute))
if err != nil {
logger.Warn(err)
}
_, err = connection.Read(messageByte)
if err != nil {
logger.Debug(err)
logger.Debug("no response")
return ""
}
return strings.TrimRight(string(messageByte), ":")
}
func fillString(retunString string, toLength int) string {
for {
lengthString := len(retunString)
if lengthString < toLength {
retunString = retunString + ":"
continue
}
break
}
return retunString
}
// chanFromConn creates a channel from a Conn object, and sends everything it
// Read()s from the socket to the channel.
func chanFromConn(conn net.Conn) chan []byte {
c := make(chan []byte)
go func() {
b := make([]byte, BUFFERSIZE)
for {
n, err := conn.Read(b)
if n > 0 {
res := make([]byte, n)
// Copy the buffer so it doesn't get changed while read by the recipient.
copy(res, b[:n])
c <- res
}
if err != nil {
c <- nil
break
}
}
}()
return c
}
// Pipe creates a full-duplex pipe between the two sockets and transfers data from one to the other.
func Pipe(conn1 net.Conn, conn2 net.Conn) {
chan1 := chanFromConn(conn1)
chan2 := chanFromConn(conn2)
for {
select {
case b1 := <-chan1:
if b1 == nil {
return
}
conn2.Write(b1)
case b2 := <-chan2:
if b2 == nil {
return
}
conn1.Write(b2)
}
}
}

1
src/models.go Normal file
View File

@ -0,0 +1 @@
package croc

View File

@ -1,135 +0,0 @@
<p align="center">
<img
src="https://user-images.githubusercontent.com/6550035/31846899-2b8a7034-b5cf-11e7-9643-afe552226c59.png"
width="100%" border="0" alt="croc">
<br>
<a href="https://github.com/schollz/croc/releases/latest"><img src="https://img.shields.io/badge/version-β2.0.0-brightgreen.svg?style=flat-square" alt="Version"></a>
<a href="https://saythanks.io/to/schollz"><img src="https://img.shields.io/badge/Say%20Thanks-!-yellow.svg?style=flat-square" alt="Go Report Card"></a>
</p>
<p align="center">Easily and securely transfer stuff from one computer to another.</p>
*croc* allows any two computers to directly and securely transfer files and folders. When sending a file, *croc* generates a random code phrase which must be shared with the recipient so they can receive the file. The code phrase encrypts all data and metadata and also serves to authorize the connection between the two computers in a intermediary relay. The relay connects the TCP ports between the two computers and does not store any information (and all information passing through it is encrypted).
## New version released June 24th, 2018 - please upgrade if you are using the public relay.
I hear you asking, *Why another open-source peer-to-peer file transfer utilities?* [There](https://github.com/cowbell/sharedrop) [are](https://github.com/webtorrent/instant.io) [great](https://github.com/kern/filepizza) [tools](https://github.com/warner/magic-wormhole) [that](https://github.com/zerotier/toss) [already](https://github.com/ipfs/go-ipfs) [do](https://github.com/zerotier/toss) [this](https://github.com/nils-werner/zget). But, after review, [I found it was useful to make another](https://schollz.github.io/sending-a-file/). Namely, *croc* has no dependencies (just [download a binary and run](https://github.com/schollz/croc/releases/latest)), it works on any operating system, and its blazingly fast because it does parallel transfer over multiple TCP ports.
# Example
_These two gifs should run in sync if you force-reload (Ctl+F5)_
**Sender:**
![send](https://raw.githubusercontent.com/schollz/croc/master/logo/sender.gif)
**Receiver:**
![receive](https://raw.githubusercontent.com/schollz/croc/master/logo/receiver.gif)
**Sender:**
```
$ croc -send some-file-or-folder
Sending 4.4 MB file named 'some-file-or-folder'
Code is: cement-galaxy-alpha
Your public key: F9Ky3WU2yG4y7KKppF4KnEhrmtY9ZlTsEMkqXfC1
Send to public key: xHVRlQ2Yp6otQXBoLMcUJmmtNPXl7z8tOf019sGw
ok? (y/n): y
Sending (->[1]63982)..
89% |███████████████████████████████████ | [12s:1s]
File sent (2.6 MB/s)
```
**Receiver:**
```
$ croc
Your public key: xHVRlQ2Yp6otQXBoLMcUJmmtNPXl7z8tOf019sGw
Enter receive code: cement-galaxy-alpha
Receiving file (4.4 MB) into: some-file-or-folder
from public key: F9Ky3WU2yG4y7KKppF4KnEhrmtY9ZlTsEMkqXfC1
ok? (y/n): y
Receiving (<-[1]63975)..
97% |██████████████████████████████████████ | [13s:0s]
Received file written to some-file-or-folder (2.6 MB/s)
```
Note, by default, you don't need any arguments for receiving! This makes it possible for you to just double click the executable to run (nice for those of us that aren't computer wizards).
## Using *croc* in pipes
You can easily use *croc* in pipes when you need to send data through stdin or get data from stdout.
**Sender:**
```
$ cat some_file_or_folder | croc
```
In this case *croc* will automatically use the stdin data and send and assign a filename like "croc-stdin-123456789".
**Receiver:**
```
$ croc --code code-phrase --yes --stdout | more
```
Here the reciever specified the code (`--code`) so it will not be prompted, and also specified `--yes` so the file will be automatically accepted. The output goes to stdout when flagged with `--stdout`.
# Install
[Download the latest release for your system](https://github.com/schollz/croc/releases/latest).
Or, you can [install Go](https://golang.org/dl/) and build from source with `go get github.com/schollz/croc`.
# How does it work?
*croc* is similar to [magic-wormhole](https://github.com/warner/magic-wormhole#design) in spirit and design. Like *magic-wormhole*, *croc* generates a code phrase for you to share with your friend which allows secure end-to-end transfering of files and folders through a intermediary relay that connects the TCP ports between the two computers. The standard relay is on a public IP address (default `cowyo.com`), but before transmitting the file the two instances of *croc* send out UDP broadcasts to determine if they are both on the local network, and use a local relay instead of the cloud relay in the case that they are both local.
The code phrase for transfering files is just three words which are 16 random bits that are [menemonic encoded](http://web.archive.org/web/20101031205747/http://www.tothink.com/mnemonic/). This code phrase is hashed using sha256 and sent to the relay which maps that hashed code phrase to that connection. When the relay finds a matching code phrase hash for both the receiver and the sender (i.e. they both have the same code phrase), then the sender transmits the encrypted metadata to the receiver through the relay. Then the receiver decrypts and reviews the metadata (file name, size), and chooses whether to consent to the transfer.
After the receiver consents to the transfer, the sender transmits encrypted data through the relay. The relay setups up [Go channels](https://golang.org/doc/effective_go.html?h=chan#channels) for each connection which pipes all the data incoming from that sender's connection out to the receiver's connection. After the transmission the channels are destroyed and all the connection and meta data information is wiped from the relay server. The encrypted file data never is stored on the relay.
**Encryption**
Encryption uses AES-256 with a pbkdf2 derived key (see [RFC2898](http://www.ietf.org/rfc/rfc2898.txt)) where the code phrase shared between the sender and receiver is used as the passphrase. For each of the two encrypted data blocks (metadata stored on relay server, and file data transmitted), a random 8-byte salt is used and a IV is generated according to [NIST Recommendation for Block ciphers, Section 8.2](http://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf).
**Decryption**
On the receiver's computer, each piece of received encrypted data is written to a separate file. These files are concatenated and then decrypted. The hash of the decrypted file is then checked against the hash transmitted from the sender (part of the meta data block).
## Run your own relay
*croc* relies on a TCP relay to staple the parallel incoming and outgoing connections. The relay temporarily stores connection information and the encrypted meta information. The default uses a public relay at, `cowyo.com`, which has a 30-day uptime of 99.989% ([click here to check the current status of the public relay](https://stats.uptimerobot.com/lOwJYIgRm)).
You can also run your own relay, it is very easy. On your server, `your-server.com`, just run
```
$ croc -relay
```
Now, when you use *croc* to send and receive you should add `-server your-server.com` to use your relay server. Make sure to open up TCP ports 27001-27009.
# Contribute
I am awed by all the [great contributions](#acknowledgements) made! If you feel like contributing, in any way, by all means you can send an Issue, a PR, ask a question, or tweet me ([@yakczar](http://ctt.ec/Rq054)).
# License
MIT
# Acknowledgements
Thanks...
- ...[@warner](https://github.com/warner) for the [idea](https://github.com/warner/magic-wormhole).
- ...[@tscholl2](https://github.com/tscholl2) for the [encryption gists](https://gist.github.com/tscholl2/dc7dc15dc132ea70a98e8542fefffa28).
- ...[@skorokithakis](https://github.com/skorokithakis) for [code on proxying two connections](https://www.stavros.io/posts/proxying-two-connections-go/).
- ...for making pull requests [@Girbons](https://github.com/Girbons), [@techtide](https://github.com/techtide), [@heymatthew](https://github.com/heymatthew), [@Lunsford94](https://github.com/Lunsford94), [@lummie](https://github.com/lummie), [@jesuiscamille](https://github.com/jesuiscamille), [@threefjord](https://github.com/threefjord), [@marcossegovia](https://github.com/marcossegovia), [@csleong98](https://github.com/csleong98), [@afotescu](https://github.com/afotescu), [@callmefever](https://github.com/callmefever), [@El-JojA](https://github.com/El-JojA), [@anatolyyyyyy](https://github.com/anatolyyyyyy), [@goggle](https://github.com/goggle), [@smileboywtu](https://github.com/smileboywtu)!

View File

@ -1 +0,0 @@
Some simple text to see if it works

View File

@ -1 +0,0 @@
More data to see if it 100% works

214
utils.go
View File

@ -1,214 +0,0 @@
package main
import (
"crypto/md5"
"fmt"
"io"
"math"
math_rand "math/rand"
"net"
"os"
"strconv"
"time"
"github.com/pkg/errors"
"github.com/schollz/bytetoword"
)
// HashWords returns word after hashing
func HashWords(s string) string {
hasher := md5.New()
hasher.Write([]byte(s))
return bytetoword.EncodeToString(hasher.Sum(nil))
}
// CatFiles copies data from n files to a single one and removes source files
// if Debug mode is set to false
func CatFiles(files []string, outfile string, remove bool) error {
finished, err := os.Create(outfile)
if err != nil {
return errors.Wrap(err, "CatFiles create: ")
}
defer finished.Close()
for _, file := range files {
fh, err := os.Open(file)
if err != nil {
return errors.Wrap(err, fmt.Sprintf("CatFiles open %v: ", file))
}
_, err = io.Copy(finished, fh)
if err != nil {
return errors.Wrap(err, "CatFiles copy: ")
}
fh.Close()
if remove {
os.Remove(file)
}
}
return nil
}
// SplitFile creates a bunch of smaller files with the data from source splited into them
func SplitFile(fileName string, numPieces int) (err error) {
file, err := os.Open(fileName)
if err != nil {
return err
}
defer file.Close()
fi, err := file.Stat()
if err != nil {
return err
}
bytesPerPiece := int(math.Ceil(float64(fi.Size()) / float64(numPieces)))
buf := make([]byte, bytesPerPiece)
for i := 0; i < numPieces; i++ {
out, err := os.Create(fileName + "." + strconv.Itoa(i))
if err != nil {
return err
}
n, err := file.Read(buf)
out.Write(buf[:n])
out.Close()
if err == io.EOF {
break
}
}
return nil
}
// CopyFile copies a file from src to dst. If src and dst files exist, and are
// the same, then return success. Otherise, attempt to create a hard link
// between the two files. If that fail, copy the file contents from src to dst.
func CopyFile(src, dst string) (err error) {
sfi, err := os.Stat(src)
if err != nil {
return
}
if !sfi.Mode().IsRegular() {
// cannot copy non-regular files (e.g., directories,
// symlinks, devices, etc.)
return fmt.Errorf("CopyFile: non-regular source file %s (%q)", sfi.Name(), sfi.Mode().String())
}
dfi, err := os.Stat(dst)
if err != nil {
if !os.IsNotExist(err) {
return
}
} else {
if !(dfi.Mode().IsRegular()) {
return fmt.Errorf("CopyFile: non-regular destination file %s (%q)", dfi.Name(), dfi.Mode().String())
}
if os.SameFile(sfi, dfi) {
return
}
}
if err = os.Link(src, dst); err == nil {
return
}
err = copyFileContents(src, dst)
return
}
// copyFileContents copies the contents of the file named src to the file named
// by dst. The file will be created if it does not already exist. If the
// destination file exists, all it's contents will be replaced by the contents
// of the source file.
func copyFileContents(src, dst string) (err error) {
in, err := os.Open(src)
if err != nil {
return
}
defer in.Close()
out, err := os.Create(dst)
if err != nil {
return
}
defer func() {
cerr := out.Close()
if err == nil {
err = cerr
}
}()
if _, err = io.Copy(out, in); err != nil {
return
}
err = out.Sync()
return
}
// HashFile does a md5 hash on the file
// from https://golang.org/pkg/crypto/md5/#example_New_file
func HashFile(filename string) (hash string, err error) {
f, err := os.Open(filename)
if err != nil {
return
}
defer f.Close()
h := md5.New()
if _, err = io.Copy(h, f); err != nil {
return
}
hash = fmt.Sprintf("%x", h.Sum(nil))
return
}
// FileSize returns the size of a file
func FileSize(filename string) (int, error) {
fi, err := os.Stat(filename)
if err != nil {
return -1, err
}
size := int(fi.Size())
return size, nil
}
// GetLocalIP returns the local ip address
func GetLocalIP() string {
addrs, err := net.InterfaceAddrs()
if err != nil {
return ""
}
bestIP := ""
for _, address := range addrs {
// check the address type and if it is not a loopback the display it
if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
if ipnet.IP.To4() != nil {
return ipnet.IP.String()
}
}
}
return bestIP
}
// src is seeds the random generator for generating random strings
var src = math_rand.NewSource(time.Now().UnixNano())
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
const (
letterIdxBits = 6 // 6 bits to represent a letter index
letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
letterIdxMax = 63 / letterIdxBits // # of letter indices fitting in 63 bits
)
// RandStringBytesMaskImprSrc prints a random string
func RandStringBytesMaskImprSrc(n int) string {
b := make([]byte, n)
// A src.Int63() generates 63 random bits, enough for letterIdxMax characters!
for i, cache, remain := n-1, src.Int63(), letterIdxMax; i >= 0; {
if remain == 0 {
cache, remain = src.Int63(), letterIdxMax
}
if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
b[i] = letterBytes[idx]
i--
}
cache >>= letterIdxBits
remain--
}
return string(b)
}

View File

@ -1,119 +0,0 @@
package main
import (
"os"
"testing"
)
func TestSplitFile(t *testing.T) {
err := SplitFile("testing_data/README.md", 3)
if err != nil {
t.Error(err)
}
os.Remove("testing_data/README.md.0")
os.Remove("testing_data/README.md.1")
}
func TestFileSize(t *testing.T) {
t.Run("File is ok ", func(t *testing.T) {
_, err := FileSize("testing_data/README.md")
if err != nil {
t.Errorf("should pass with no error, got: %v", err)
}
})
t.Run("File does not exist", func(t *testing.T) {
s, err := FileSize("testing_data/someStrangeFile")
if err == nil {
t.Error("should return an error")
}
if s != -1 {
t.Errorf("size should be -1, got: %d", s)
}
})
}
func TestHashFile(t *testing.T) {
t.Run("Hash created successfully", func(t *testing.T) {
h, err := HashFile("testing_data/README.md")
if err != nil {
t.Errorf("should pass with no error, got: %v", err)
}
if len(h) != 32 {
t.Errorf("invalid md5 hash, length should be 32 got: %d", len(h))
}
})
t.Run("File does not exist", func(t *testing.T) {
h, err := HashFile("testing_data/someStrangeFile")
if err == nil {
t.Error("should return an error")
}
if len(h) > 0 {
t.Errorf("hash length should be 0, got: %d", len(h))
}
if h != "" {
t.Errorf("hash should be empty string, got: %s", h)
}
})
}
func TestCopyFileContents(t *testing.T) {
t.Run("Content copied successfully", func(t *testing.T) {
f1 := "testing_data/README.md"
f2 := "testing_data/CopyOfREADME.md"
err := copyFileContents(f1, f2)
if err != nil {
t.Errorf("should pass with no error, got: %v", err)
}
f1Length, err := FileSize(f1)
if err != nil {
t.Errorf("can't get file nr1 size: %v", err)
}
f2Length, err := FileSize(f2)
if err != nil {
t.Errorf("can't get file nr2 size: %v", err)
}
if f1Length != f2Length {
t.Errorf("size of both files should be same got: file1: %d file2: %d", f1Length, f2Length)
}
os.Remove(f2)
})
}
func TestCopyFile(t *testing.T) {
t.Run("Files copied successfully", func(t *testing.T) {
f1 := "testing_data/README.md"
f2 := "testing_data/CopyOfREADME.md"
err := CopyFile(f1, f2)
if err != nil {
t.Errorf("should pass with no error, got: %v", err)
}
f1Length, err := FileSize(f1)
if err != nil {
t.Errorf("can't get file nr1 size: %v", err)
}
f2Length, err := FileSize(f2)
if err != nil {
t.Errorf("can't get file nr2 size: %v", err)
}
if f1Length != f2Length {
t.Errorf("size of both files should be same got: file1: %d file2: %d", f1Length, f2Length)
}
os.Remove(f2)
})
}
func TestCatFiles(t *testing.T) {
t.Run("CatFiles passing", func(t *testing.T) {
files := []string{"testing_data/catFile1.txt", "testing_data/catFile2.txt"}
err := CatFiles(files, "testing_data/CatFile.txt", false)
if err != nil {
t.Errorf("should pass with no error, got: %v", err)
}
if _, err := os.Stat("testing_data/CatFile.txt"); os.IsNotExist(err) {
t.Errorf("file were not created: %v", err)
}
os.Remove("testing_data/CatFile.txt")
})
}

View File

@ -1,29 +0,0 @@
# Created by .gitignore support plugin (hsz.mobi)
### Go template
# 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
.idea
*.iml

View File

@ -1,15 +0,0 @@
language: go
go:
- 1.6.3
- 1.7.3
env:
- GOARCH: amd64
- GOARCH: 386
script:
- go test -v
notifications:
email:
recipients:
- brian.downs@gmail.com
on_success: change
on_failure: always

View File

@ -1,174 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.

View File

@ -1,281 +0,0 @@
# Spinner
[![GoDoc](https://godoc.org/github.com/briandowns/spinner?status.svg)](https://godoc.org/github.com/briandowns/spinner) [![Build Status](https://travis-ci.org/briandowns/spinner.svg?branch=master)](https://travis-ci.org/briandowns/spinner)
spinner is a simple package to add a spinner / progress indicator to any terminal application. Examples can be found below as well as full examples in the examples directory.
For more detail about the library and its features, reference your local godoc once installed.
Contributions welcome!
## Installation
```bash
go get github.com/briandowns/spinner
```
## Available Character Sets
(Numbered by their slice index)
index | character set
------|---------------
0 | ```←↖↑↗→↘↓↙```
1 | ```▁▃▄▅▆▇█▇▆▅▄▃▁```
2 | ```▖▘▝▗```
3 | ```┤┘┴└├┌┬┐```
4 | ```◢◣◤◥```
5 | ```◰◳◲◱```
6 | ```◴◷◶◵```
7 | ```◐◓◑◒```
8 | ```.oO@*```
9 | ```|/-\```
10 | ```◡◡⊙⊙◠◠```
11 | ```⣾⣽⣻⢿⡿⣟⣯⣷```
12 | ```>))'> >))'> >))'> >))'> >))'> <'((< <'((< <'((<```
13 | ```⠁⠂⠄⡀⢀⠠⠐⠈```
14 | ```⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏```
15 | ```abcdefghijklmnopqrstuvwxyz```
16 | ```▉▊▋▌▍▎▏▎▍▌▋▊▉```
17 | ```■□▪▫```
18 | ```←↑→↓```
19 | ```╫╪```
20 | ```⇐⇖⇑⇗⇒⇘⇓⇙```
21 | ```⠁⠁⠉⠙⠚⠒⠂⠂⠒⠲⠴⠤⠄⠄⠤⠠⠠⠤⠦⠖⠒⠐⠐⠒⠓⠋⠉⠈⠈```
22 | ```⠈⠉⠋⠓⠒⠐⠐⠒⠖⠦⠤⠠⠠⠤⠦⠖⠒⠐⠐⠒⠓⠋⠉⠈```
23 | ```⠁⠉⠙⠚⠒⠂⠂⠒⠲⠴⠤⠄⠄⠤⠴⠲⠒⠂⠂⠒⠚⠙⠉⠁```
24 | ```⠋⠙⠚⠒⠂⠂⠒⠲⠴⠦⠖⠒⠐⠐⠒⠓⠋```
25 | ```ヲァィゥェォャュョッアイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワン```
26 | ```. .. ...```
27 | ```▁▂▃▄▅▆▇█▉▊▋▌▍▎▏▏▎▍▌▋▊▉█▇▆▅▄▃▂▁```
28 | ```.oO°Oo.```
29 | ```+x```
30 | ```v<^>```
31 | ```>>---> >>---> >>---> >>---> >>---> <---<< <---<< <---<< <---<< <---<<```
32 | ```| || ||| |||| ||||| |||||| ||||| |||| ||| || |```
33 | ```[] [=] [==] [===] [====] [=====] [======] [=======] [========] [=========] [==========]```
34 | ```(*---------) (-*--------) (--*-------) (---*------) (----*-----) (-----*----) (------*---) (-------*--) (--------*-) (---------*)```
35 | ```█▒▒▒▒▒▒▒▒▒ ███▒▒▒▒▒▒▒ █████▒▒▒▒▒ ███████▒▒▒ ██████████```
36 | ```[ ] [=> ] [===> ] [=====> ] [======> ] [========> ] [==========> ] [============> ] [==============> ] [================> ] [==================> ] [===================>]```
37 | ```🕐 🕑 🕒 🕓 🕔 🕕 🕖 🕗 🕘 🕙 🕚 🕛```
38 | ```🕐 🕜 🕑 🕝 🕒 🕞 🕓 🕟 🕔 🕠 🕕 🕡 🕖 🕢 🕗 🕣 🕘 🕤 🕙 🕥 🕚 🕦 🕛 🕧```
39 | ```🌍 🌎 🌏```
40 | ```◜ ◝ ◞ ◟```
41 | ```⬒ ⬔ ⬓ ⬕```
42 | ```⬖ ⬘ ⬗ ⬙```
43 | ```[>>> >] []>>>> [] [] >>>> [] [] >>>> [] [] >>>> [] [] >>>>[] [>> >>]```
## Features
* Start
* Stop
* Restart
* Reverse direction
* Update the spinner character set
* Update the spinner speed
* Prefix or append text
* Change spinner color, background, and text attributes such as bold / italics
* Get spinner status
* Chain, pipe, redirect output
* Output final string on spinner/indicator completion
## Examples
```Go
package main
import (
"github.com/briandowns/spinner"
"time"
)
func main() {
s := spinner.New(spinner.CharSets[9], 100*time.Millisecond) // Build our new spinner
s.Start() // Start the spinner
time.Sleep(4 * time.Second) // Run for some time to simulate work
s.Stop()
}
```
## Update the character set and restart the spinner
```Go
s.UpdateCharSet(spinner.CharSets[1]) // Update spinner to use a different character set
s.Restart() // Restart the spinner
time.Sleep(4 * time.Second)
s.Stop()
```
## Update spin speed and restart the spinner
```Go
s.UpdateSpeed(200 * time.Millisecond) // Update the speed the spinner spins at
s.Restart()
time.Sleep(4 * time.Second)
s.Stop()
```
## Reverse the direction of the spinner
```Go
s.Reverse() // Reverse the direction the spinner is spinning
s.Restart()
time.Sleep(4 * time.Second)
s.Stop()
```
## Provide your own spinner
(or send me an issue or pull request to add to the project)
```Go
someSet := []string{"+", "-"}
s := spinner.New(someSet, 100*time.Millisecond)
```
## Prefix or append text to the spinner
```Go
s.Prefix = "prefixed text: " // Prefix text before the spinner
s.Suffix = " :appended text" // Append text after the spinner
```
## Set or change the color of the spinner. Default color is white. This will restart the spinner with the new color.
```Go
s.Color("red") // Set the spinner color to red
```
You can specify both the background and foreground color, as well as additional attributes such as `bold` or `underline`.
```Go
s.Color("red", "bold") // Set the spinner color to a bold red
```
Or to set the background to black, the foreground to a bold red:
```Go
s.Color("bgBlack", "bold", "fgRed")
```
Below is the full color and attribute list:
```
// default colors
red
black
green
yellow
blue
magenta
cyan
white
// attributes
reset
bold
faint
italic
underline
blinkslow
blinkrapid
reversevideo
concealed
crossedout
// foreground text
fgBlack
fgRed
fgGreen
fgYellow
fgBlue
fgMagenta
fgCyan
fgWhite
// foreground Hi-Intensity text
fgHiBlack
fgHiRed
fgHiGreen
fgHiYellow
fgHiBlue
fgHiMagenta
fgHiCyan
fgHiWhite
// background text
bgBlack
bgRed
bgGreen
bgYellow
bgBlue
bgMagenta
bgCyan
bgWhite
// background Hi-Intensity text
bgHiBlack
bgHiRed
bgHiGreen
bgHiYellow
bgHiBlue
bgHiMagenta
bgHiCyan
bgHiWhite
```
## Generate a sequence of numbers
```Go
setOfDigits := spinner.GenerateNumberSequence(25) // Generate a 25 digit string of numbers
s := spinner.New(setOfDigits, 100*time.Millisecond)
```
## Get spinner status
```Go
fmt.Println(s.Active())
```
## Unix pipe and redirect
Feature suggested and write up by [dekz](https://github.com/dekz)
Setting the Spinner Writer to Stderr helps show progress to the user, with the enhancement to chain, pipe or redirect the output.
```go
s := spinner.New(spinner.CharSets[11], 100*time.Millisecond)
s.Suffix = " Encrypting data..."
s.Writer = os.Stderr
s.Start()
// Encrypt the data into ciphertext
fmt.Println(os.Stdout, ciphertext)
```
```sh
> myprog encrypt "Secret text" > encrypted.txt
⣯ Encrypting data...
```
```sh
> cat encrypted.txt
1243hjkbas23i9ah27sj39jghv237n2oa93hg83
```
## Final String Output
Add additional output when the spinner/indicator has completed. The "final" output string can be multi-lined and will be written to wherever the `io.Writer` has been configured for.
```Go
s := spinner.New(spinner.CharSets[9], 100*time.Millisecond)
s.FinalMSG = "Complete!\nNew line!\nAnother one!\n"
s.Start()
time.Sleep(4 * time.Second)
s.Stop()
```
Output
```sh
Complete!
New line!
Another one!
```

View File

@ -1,59 +0,0 @@
package spinner
const (
clockOneOClock = '\U0001F550'
clockOneThirty = '\U0001F55C'
)
// CharSets contains the available character sets
var CharSets = map[int][]string{
0: {"←", "↖", "↑", "↗", "→", "↘", "↓", "↙"},
1: {"▁", "▃", "▄", "▅", "▆", "▇", "█", "▇", "▆", "▅", "▄", "▃", "▁"},
2: {"▖", "▘", "▝", "▗"},
3: {"┤", "┘", "┴", "└", "├", "┌", "┬", "┐"},
4: {"◢", "◣", "◤", "◥"},
5: {"◰", "◳", "◲", "◱"},
6: {"◴", "◷", "◶", "◵"},
7: {"◐", "◓", "◑", "◒"},
8: {".", "o", "O", "@", "*"},
9: {"|", "/", "-", "\\"},
10: {"◡◡", "⊙⊙", "◠◠"},
11: {"⣾", "⣽", "⣻", "⢿", "⡿", "⣟", "⣯", "⣷"},
12: {">))'>", " >))'>", " >))'>", " >))'>", " >))'>", " <'((<", " <'((<", " <'((<"},
13: {"⠁", "⠂", "⠄", "⡀", "⢀", "⠠", "⠐", "⠈"},
14: {"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"},
15: {"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"},
16: {"▉", "▊", "▋", "▌", "▍", "▎", "▏", "▎", "▍", "▌", "▋", "▊", "▉"},
17: {"■", "□", "▪", "▫"},
18: {"←", "↑", "→", "↓"},
19: {"╫", "╪"},
20: {"⇐", "⇖", "⇑", "⇗", "⇒", "⇘", "⇓", "⇙"},
21: {"⠁", "⠁", "⠉", "⠙", "⠚", "⠒", "⠂", "⠂", "⠒", "⠲", "⠴", "⠤", "⠄", "⠄", "⠤", "⠠", "⠠", "⠤", "⠦", "⠖", "⠒", "⠐", "⠐", "⠒", "⠓", "⠋", "⠉", "⠈", "⠈"},
22: {"⠈", "⠉", "⠋", "⠓", "⠒", "⠐", "⠐", "⠒", "⠖", "⠦", "⠤", "⠠", "⠠", "⠤", "⠦", "⠖", "⠒", "⠐", "⠐", "⠒", "⠓", "⠋", "⠉", "⠈"},
23: {"⠁", "⠉", "⠙", "⠚", "⠒", "⠂", "⠂", "⠒", "⠲", "⠴", "⠤", "⠄", "⠄", "⠤", "⠴", "⠲", "⠒", "⠂", "⠂", "⠒", "⠚", "⠙", "⠉", "⠁"},
24: {"⠋", "⠙", "⠚", "⠒", "⠂", "⠂", "⠒", "⠲", "⠴", "⠦", "⠖", "⠒", "⠐", "⠐", "⠒", "⠓", "⠋"},
25: {"ヲ", "ァ", "ィ", "ゥ", "ェ", "ォ", "ャ", "ュ", "ョ", "ッ", "ア", "イ", "ウ", "エ", "オ", "カ", "キ", "ク", "ケ", "コ", "サ", "シ", "ス", "セ", "ソ", "タ", "チ", "ツ", "テ", "ト", "ナ", "ニ", "ヌ", "ネ", "ノ", "ハ", "ヒ", "フ", "ヘ", "ホ", "マ", "ミ", "ム", "メ", "モ", "ヤ", "ユ", "ヨ", "ラ", "リ", "ル", "レ", "ロ", "ワ", "ン"},
26: {".", "..", "..."},
27: {"▁", "▂", "▃", "▄", "▅", "▆", "▇", "█", "▉", "▊", "▋", "▌", "▍", "▎", "▏", "▏", "▎", "▍", "▌", "▋", "▊", "▉", "█", "▇", "▆", "▅", "▄", "▃", "▂", "▁"},
28: {".", "o", "O", "°", "O", "o", "."},
29: {"+", "x"},
30: {"v", "<", "^", ">"},
31: {">>--->", " >>--->", " >>--->", " >>--->", " >>--->", " <---<<", " <---<<", " <---<<", " <---<<", "<---<<"},
32: {"|", "||", "|||", "||||", "|||||", "|||||||", "||||||||", "|||||||", "||||||", "|||||", "||||", "|||", "||", "|"},
33: {"[ ]", "[= ]", "[== ]", "[=== ]", "[==== ]", "[===== ]", "[====== ]", "[======= ]", "[======== ]", "[========= ]", "[==========]"},
34: {"(*---------)", "(-*--------)", "(--*-------)", "(---*------)", "(----*-----)", "(-----*----)", "(------*---)", "(-------*--)", "(--------*-)", "(---------*)"},
35: {"█▒▒▒▒▒▒▒▒▒", "███▒▒▒▒▒▒▒", "█████▒▒▒▒▒", "███████▒▒▒", "██████████"},
36: {"[ ]", "[=> ]", "[===> ]", "[=====> ]", "[======> ]", "[========> ]", "[==========> ]", "[============> ]", "[==============> ]", "[================> ]", "[==================> ]", "[===================>]"},
39: {"🌍", "🌎", "🌏"},
40: {"◜", "◝", "◞", "◟"},
41: {"⬒", "⬔", "⬓", "⬕"},
42: {"⬖", "⬘", "⬗", "⬙"},
43: {"[>>> >]", "[]>>>> []", "[] >>>> []", "[] >>>> []", "[] >>>> []", "[] >>>>[]", "[>> >>]"},
}
func init() {
for i := rune(0); i < 12; i++ {
CharSets[37] = append(CharSets[37], string([]rune{clockOneOClock + i}))
CharSets[38] = append(CharSets[38], string([]rune{clockOneOClock + i}), string([]rune{clockOneThirty + i}))
}
}

View File

@ -1,318 +0,0 @@
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package spinner is a simple package to add a spinner / progress indicator to any terminal application.
package spinner
import (
"errors"
"fmt"
"io"
"strconv"
"sync"
"time"
"unicode/utf8"
"encoding/hex"
"github.com/fatih/color"
)
// errInvalidColor is returned when attempting to set an invalid color
var errInvalidColor = errors.New("invalid color")
// validColors holds an array of the only colors allowed
var validColors = map[string]bool{
// default colors for backwards compatibility
"black": true,
"red": true,
"green": true,
"yellow": true,
"blue": true,
"magenta": true,
"cyan": true,
"white": true,
// attributes
"reset": true,
"bold": true,
"faint": true,
"italic": true,
"underline": true,
"blinkslow": true,
"blinkrapid": true,
"reversevideo": true,
"concealed": true,
"crossedout": true,
// foreground text
"fgBlack": true,
"fgRed": true,
"fgGreen": true,
"fgYellow": true,
"fgBlue": true,
"fgMagenta": true,
"fgCyan": true,
"fgWhite": true,
// foreground Hi-Intensity text
"fgHiBlack": true,
"fgHiRed": true,
"fgHiGreen": true,
"fgHiYellow": true,
"fgHiBlue": true,
"fgHiMagenta": true,
"fgHiCyan": true,
"fgHiWhite": true,
// background text
"bgBlack": true,
"bgRed": true,
"bgGreen": true,
"bgYellow": true,
"bgBlue": true,
"bgMagenta": true,
"bgCyan": true,
"bgWhite": true,
// background Hi-Intensity text
"bgHiBlack": true,
"bgHiRed": true,
"bgHiGreen": true,
"bgHiYellow": true,
"bgHiBlue": true,
"bgHiMagenta": true,
"bgHiCyan": true,
"bgHiWhite": true,
}
// returns a valid color's foreground text color attribute
var colorAttributeMap = map[string]color.Attribute{
// default colors for backwards compatibility
"black": color.FgBlack,
"red": color.FgRed,
"green": color.FgGreen,
"yellow": color.FgYellow,
"blue": color.FgBlue,
"magenta": color.FgMagenta,
"cyan": color.FgCyan,
"white": color.FgWhite,
// attributes
"reset": color.Reset,
"bold": color.Bold,
"faint": color.Faint,
"italic": color.Italic,
"underline": color.Underline,
"blinkslow": color.BlinkSlow,
"blinkrapid": color.BlinkRapid,
"reversevideo": color.ReverseVideo,
"concealed": color.Concealed,
"crossedout": color.CrossedOut,
// foreground text colors
"fgBlack": color.FgBlack,
"fgRed": color.FgRed,
"fgGreen": color.FgGreen,
"fgYellow": color.FgYellow,
"fgBlue": color.FgBlue,
"fgMagenta": color.FgMagenta,
"fgCyan": color.FgCyan,
"fgWhite": color.FgWhite,
// foreground Hi-Intensity text colors
"fgHiBlack": color.FgHiBlack,
"fgHiRed": color.FgHiRed,
"fgHiGreen": color.FgHiGreen,
"fgHiYellow": color.FgHiYellow,
"fgHiBlue": color.FgHiBlue,
"fgHiMagenta": color.FgHiMagenta,
"fgHiCyan": color.FgHiCyan,
"fgHiWhite": color.FgHiWhite,
// background text colors
"bgBlack": color.BgBlack,
"bgRed": color.BgRed,
"bgGreen": color.BgGreen,
"bgYellow": color.BgYellow,
"bgBlue": color.BgBlue,
"bgMagenta": color.BgMagenta,
"bgCyan": color.BgCyan,
"bgWhite": color.BgWhite,
// background Hi-Intensity text colors
"bgHiBlack": color.BgHiBlack,
"bgHiRed": color.BgHiRed,
"bgHiGreen": color.BgHiGreen,
"bgHiYellow": color.BgHiYellow,
"bgHiBlue": color.BgHiBlue,
"bgHiMagenta": color.BgHiMagenta,
"bgHiCyan": color.BgHiCyan,
"bgHiWhite": color.BgHiWhite,
}
// validColor will make sure the given color is actually allowed
func validColor(c string) bool {
valid := false
if validColors[c] {
valid = true
}
return valid
}
// Spinner struct to hold the provided options
type Spinner struct {
Delay time.Duration // Delay is the speed of the indicator
chars []string // chars holds the chosen character set
Prefix string // Prefix is the text preppended to the indicator
Suffix string // Suffix is the text appended to the indicator
FinalMSG string // string displayed after Stop() is called
lastOutput string // last character(set) written
color func(a ...interface{}) string // default color is white
lock *sync.RWMutex //
Writer io.Writer // to make testing better, exported so users have access
active bool // active holds the state of the spinner
stopChan chan struct{} // stopChan is a channel used to stop the indicator
}
// New provides a pointer to an instance of Spinner with the supplied options
func New(cs []string, d time.Duration) *Spinner {
return &Spinner{
Delay: d,
chars: cs,
color: color.New(color.FgWhite).SprintFunc(),
lock: &sync.RWMutex{},
Writer: color.Output,
active: false,
stopChan: make(chan struct{}, 1),
}
}
// Active will return whether or not the spinner is currently active
func (s *Spinner) Active() bool {
return s.active
}
// Start will start the indicator
func (s *Spinner) Start() {
if s.active {
return
}
s.active = true
go func() {
for {
for i := 0; i < len(s.chars); i++ {
select {
case <-s.stopChan:
return
default:
s.lock.Lock()
s.erase()
outColor := fmt.Sprintf("%s%s%s ", s.Prefix, s.color(s.chars[i]), s.Suffix)
outPlain := fmt.Sprintf("%s%s%s ", s.Prefix, s.chars[i], s.Suffix)
fmt.Fprint(s.Writer, outColor)
s.lastOutput = outPlain
delay := s.Delay
s.lock.Unlock()
time.Sleep(delay)
}
}
}
}()
}
// Stop stops the indicator
func (s *Spinner) Stop() {
s.lock.Lock()
defer s.lock.Unlock()
if s.active {
s.active = false
s.erase()
if s.FinalMSG != "" {
fmt.Fprintf(s.Writer, s.FinalMSG)
}
s.stopChan <- struct{}{}
}
}
// Restart will stop and start the indicator
func (s *Spinner) Restart() {
s.Stop()
s.Start()
}
// Reverse will reverse the order of the slice assigned to the indicator
func (s *Spinner) Reverse() {
s.lock.Lock()
defer s.lock.Unlock()
for i, j := 0, len(s.chars)-1; i < j; i, j = i+1, j-1 {
s.chars[i], s.chars[j] = s.chars[j], s.chars[i]
}
}
// Color will set the struct field for the given color to be used
func (s *Spinner) Color(colors ...string) error {
colorAttributes := make([]color.Attribute, len(colors))
// Verify colours are valid and place the appropriate attribute in the array
for index, c := range colors {
if !validColor(c) {
return errInvalidColor
}
colorAttributes[index] = colorAttributeMap[c]
}
s.color = color.New(colorAttributes...).SprintFunc()
s.Restart()
return nil
}
// UpdateSpeed will set the indicator delay to the given value
func (s *Spinner) UpdateSpeed(d time.Duration) {
s.lock.Lock()
defer s.lock.Unlock()
s.Delay = d
}
// UpdateCharSet will change the current character set to the given one
func (s *Spinner) UpdateCharSet(cs []string) {
s.lock.Lock()
defer s.lock.Unlock()
s.chars = cs
}
// erase deletes written characters
//
// Caller must already hold s.lock.
func (s *Spinner) erase() {
n := utf8.RuneCountInString(s.lastOutput)
del, _ := hex.DecodeString("7f")
for _, c := range []string{"\b", string(del), "\b"} {
for i := 0; i < n; i++ {
fmt.Fprintf(s.Writer, c)
}
}
s.lastOutput = ""
}
// GenerateNumberSequence will generate a slice of integers at the
// provided length and convert them each to a string
func GenerateNumberSequence(length int) []string {
numSeq := make([]string, length)
for i := 0; i < length; i++ {
numSeq[i] = strconv.Itoa(i)
}
return numSeq
}

View File

@ -1,24 +0,0 @@
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.

View File

@ -1,113 +0,0 @@
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

View File

@ -1,129 +0,0 @@
// 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)
}
}

View File

@ -1,142 +0,0 @@
// 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)
}
}

View File

@ -1,69 +0,0 @@
// 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
}
}
}

View File

@ -1,82 +0,0 @@
// 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)
}
}

View File

@ -1,75 +0,0 @@
// 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()
}
}

View File

@ -1,188 +0,0 @@
// 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)
}

View File

@ -1,61 +0,0 @@
// 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}}
}

View File

@ -1,141 +0,0 @@
// 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

View File

@ -1,25 +0,0 @@
// 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

View File

@ -1,162 +0,0 @@
// 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"
}

View File

@ -1,194 +0,0 @@
// 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
}

View File

@ -1,194 +0,0 @@
// 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
}

View File

@ -1,31 +0,0 @@
// 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()
}

View File

@ -1,81 +0,0 @@
// 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 ""
}

View File

@ -1,242 +0,0 @@
// 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
}

View File

@ -1,189 +0,0 @@
// 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
}

View File

@ -1,66 +0,0 @@
// 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)
}

View File

@ -1,47 +0,0 @@
// 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())
}

175
vendor/github.com/cihub/seelog/doc.go generated vendored
View File

@ -1,175 +0,0 @@
// 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

View File

@ -1,461 +0,0 @@
// 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)
}
}

View File

@ -1,10 +0,0 @@
package seelog
// Base struct for custom errors.
type baseError struct {
message string
}
func (be baseError) Error() string {
return be.message
}

View File

@ -1,403 +0,0 @@
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 &notDirectoryError{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
}

View File

@ -1,175 +0,0 @@
// 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
}

307
vendor/github.com/cihub/seelog/log.go generated vendored
View File

@ -1,307 +0,0 @@
// 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()
}

View File

@ -1,370 +0,0 @@
// 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...)
}

View File

@ -1,161 +0,0 @@
// 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)
}

View File

@ -1,144 +0,0 @@
// 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
}

View File

@ -1,47 +0,0 @@
// 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"
}

View File

@ -1,92 +0,0 @@
// 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)
}

View File

@ -1,62 +0,0 @@
// 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
}

View File

@ -1,625 +0,0 @@
// 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)
}

View File

@ -1,214 +0,0 @@
// 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
}

View File

@ -1,15 +0,0 @@
ISC License
Copyright (c) 2012-2016 Dave Collins <dave@davec.name>
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.

View File

@ -1,152 +0,0 @@
// Copyright (c) 2015-2016 Dave Collins <dave@davec.name>
//
// 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.
// NOTE: Due to the following build constraints, this file will only be compiled
// when the code is not running on Google App Engine, compiled by GopherJS, and
// "-tags safe" is not added to the go build command line. The "disableunsafe"
// tag is deprecated and thus should not be used.
// +build !js,!appengine,!safe,!disableunsafe
package spew
import (
"reflect"
"unsafe"
)
const (
// UnsafeDisabled is a build-time constant which specifies whether or
// not access to the unsafe package is available.
UnsafeDisabled = false
// ptrSize is the size of a pointer on the current arch.
ptrSize = unsafe.Sizeof((*byte)(nil))
)
var (
// offsetPtr, offsetScalar, and offsetFlag are the offsets for the
// internal reflect.Value fields. These values are valid before golang
// commit ecccf07e7f9d which changed the format. The are also valid
// after commit 82f48826c6c7 which changed the format again to mirror
// the original format. Code in the init function updates these offsets
// as necessary.
offsetPtr = uintptr(ptrSize)
offsetScalar = uintptr(0)
offsetFlag = uintptr(ptrSize * 2)
// flagKindWidth and flagKindShift indicate various bits that the
// reflect package uses internally to track kind information.
//
// flagRO indicates whether or not the value field of a reflect.Value is
// read-only.
//
// flagIndir indicates whether the value field of a reflect.Value is
// the actual data or a pointer to the data.
//
// These values are valid before golang commit 90a7c3c86944 which
// changed their positions. Code in the init function updates these
// flags as necessary.
flagKindWidth = uintptr(5)
flagKindShift = uintptr(flagKindWidth - 1)
flagRO = uintptr(1 << 0)
flagIndir = uintptr(1 << 1)
)
func init() {
// Older versions of reflect.Value stored small integers directly in the
// ptr field (which is named val in the older versions). Versions
// between commits ecccf07e7f9d and 82f48826c6c7 added a new field named
// scalar for this purpose which unfortunately came before the flag
// field, so the offset of the flag field is different for those
// versions.
//
// This code constructs a new reflect.Value from a known small integer
// and checks if the size of the reflect.Value struct indicates it has
// the scalar field. When it does, the offsets are updated accordingly.
vv := reflect.ValueOf(0xf00)
if unsafe.Sizeof(vv) == (ptrSize * 4) {
offsetScalar = ptrSize * 2
offsetFlag = ptrSize * 3
}
// Commit 90a7c3c86944 changed the flag positions such that the low
// order bits are the kind. This code extracts the kind from the flags
// field and ensures it's the correct type. When it's not, the flag
// order has been changed to the newer format, so the flags are updated
// accordingly.
upf := unsafe.Pointer(uintptr(unsafe.Pointer(&vv)) + offsetFlag)
upfv := *(*uintptr)(upf)
flagKindMask := uintptr((1<<flagKindWidth - 1) << flagKindShift)
if (upfv&flagKindMask)>>flagKindShift != uintptr(reflect.Int) {
flagKindShift = 0
flagRO = 1 << 5
flagIndir = 1 << 6
// Commit adf9b30e5594 modified the flags to separate the
// flagRO flag into two bits which specifies whether or not the
// field is embedded. This causes flagIndir to move over a bit
// and means that flagRO is the combination of either of the
// original flagRO bit and the new bit.
//
// This code detects the change by extracting what used to be
// the indirect bit to ensure it's set. When it's not, the flag
// order has been changed to the newer format, so the flags are
// updated accordingly.
if upfv&flagIndir == 0 {
flagRO = 3 << 5
flagIndir = 1 << 7
}
}
}
// unsafeReflectValue converts the passed reflect.Value into a one that bypasses
// the typical safety restrictions preventing access to unaddressable and
// unexported data. It works by digging the raw pointer to the underlying
// value out of the protected value and generating a new unprotected (unsafe)
// reflect.Value to it.
//
// This allows us to check for implementations of the Stringer and error
// interfaces to be used for pretty printing ordinarily unaddressable and
// inaccessible values such as unexported struct fields.
func unsafeReflectValue(v reflect.Value) (rv reflect.Value) {
indirects := 1
vt := v.Type()
upv := unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + offsetPtr)
rvf := *(*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + offsetFlag))
if rvf&flagIndir != 0 {
vt = reflect.PtrTo(v.Type())
indirects++
} else if offsetScalar != 0 {
// The value is in the scalar field when it's not one of the
// reference types.
switch vt.Kind() {
case reflect.Uintptr:
case reflect.Chan:
case reflect.Func:
case reflect.Map:
case reflect.Ptr:
case reflect.UnsafePointer:
default:
upv = unsafe.Pointer(uintptr(unsafe.Pointer(&v)) +
offsetScalar)
}
}
pv := reflect.NewAt(vt, upv)
rv = pv
for i := 0; i < indirects; i++ {
rv = rv.Elem()
}
return rv
}

View File

@ -1,38 +0,0 @@
// Copyright (c) 2015-2016 Dave Collins <dave@davec.name>
//
// 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.
// NOTE: Due to the following build constraints, this file will only be compiled
// when the code is running on Google App Engine, compiled by GopherJS, or
// "-tags safe" is added to the go build command line. The "disableunsafe"
// tag is deprecated and thus should not be used.
// +build js appengine safe disableunsafe
package spew
import "reflect"
const (
// UnsafeDisabled is a build-time constant which specifies whether or
// not access to the unsafe package is available.
UnsafeDisabled = true
)
// unsafeReflectValue typically converts the passed reflect.Value into a one
// that bypasses the typical safety restrictions preventing access to
// unaddressable and unexported data. However, doing this relies on access to
// the unsafe package. This is a stub version which simply returns the passed
// reflect.Value when the unsafe package is not available.
func unsafeReflectValue(v reflect.Value) reflect.Value {
return v
}

View File

@ -1,341 +0,0 @@
/*
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
*
* 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.
*/
package spew
import (
"bytes"
"fmt"
"io"
"reflect"
"sort"
"strconv"
)
// Some constants in the form of bytes to avoid string overhead. This mirrors
// the technique used in the fmt package.
var (
panicBytes = []byte("(PANIC=")
plusBytes = []byte("+")
iBytes = []byte("i")
trueBytes = []byte("true")
falseBytes = []byte("false")
interfaceBytes = []byte("(interface {})")
commaNewlineBytes = []byte(",\n")
newlineBytes = []byte("\n")
openBraceBytes = []byte("{")
openBraceNewlineBytes = []byte("{\n")
closeBraceBytes = []byte("}")
asteriskBytes = []byte("*")
colonBytes = []byte(":")
colonSpaceBytes = []byte(": ")
openParenBytes = []byte("(")
closeParenBytes = []byte(")")
spaceBytes = []byte(" ")
pointerChainBytes = []byte("->")
nilAngleBytes = []byte("<nil>")
maxNewlineBytes = []byte("<max depth reached>\n")
maxShortBytes = []byte("<max>")
circularBytes = []byte("<already shown>")
circularShortBytes = []byte("<shown>")
invalidAngleBytes = []byte("<invalid>")
openBracketBytes = []byte("[")
closeBracketBytes = []byte("]")
percentBytes = []byte("%")
precisionBytes = []byte(".")
openAngleBytes = []byte("<")
closeAngleBytes = []byte(">")
openMapBytes = []byte("map[")
closeMapBytes = []byte("]")
lenEqualsBytes = []byte("len=")
capEqualsBytes = []byte("cap=")
)
// hexDigits is used to map a decimal value to a hex digit.
var hexDigits = "0123456789abcdef"
// catchPanic handles any panics that might occur during the handleMethods
// calls.
func catchPanic(w io.Writer, v reflect.Value) {
if err := recover(); err != nil {
w.Write(panicBytes)
fmt.Fprintf(w, "%v", err)
w.Write(closeParenBytes)
}
}
// handleMethods attempts to call the Error and String methods on the underlying
// type the passed reflect.Value represents and outputes the result to Writer w.
//
// It handles panics in any called methods by catching and displaying the error
// as the formatted value.
func handleMethods(cs *ConfigState, w io.Writer, v reflect.Value) (handled bool) {
// We need an interface to check if the type implements the error or
// Stringer interface. However, the reflect package won't give us an
// interface on certain things like unexported struct fields in order
// to enforce visibility rules. We use unsafe, when it's available,
// to bypass these restrictions since this package does not mutate the
// values.
if !v.CanInterface() {
if UnsafeDisabled {
return false
}
v = unsafeReflectValue(v)
}
// Choose whether or not to do error and Stringer interface lookups against
// the base type or a pointer to the base type depending on settings.
// Technically calling one of these methods with a pointer receiver can
// mutate the value, however, types which choose to satisify an error or
// Stringer interface with a pointer receiver should not be mutating their
// state inside these interface methods.
if !cs.DisablePointerMethods && !UnsafeDisabled && !v.CanAddr() {
v = unsafeReflectValue(v)
}
if v.CanAddr() {
v = v.Addr()
}
// Is it an error or Stringer?
switch iface := v.Interface().(type) {
case error:
defer catchPanic(w, v)
if cs.ContinueOnMethod {
w.Write(openParenBytes)
w.Write([]byte(iface.Error()))
w.Write(closeParenBytes)
w.Write(spaceBytes)
return false
}
w.Write([]byte(iface.Error()))
return true
case fmt.Stringer:
defer catchPanic(w, v)
if cs.ContinueOnMethod {
w.Write(openParenBytes)
w.Write([]byte(iface.String()))
w.Write(closeParenBytes)
w.Write(spaceBytes)
return false
}
w.Write([]byte(iface.String()))
return true
}
return false
}
// printBool outputs a boolean value as true or false to Writer w.
func printBool(w io.Writer, val bool) {
if val {
w.Write(trueBytes)
} else {
w.Write(falseBytes)
}
}
// printInt outputs a signed integer value to Writer w.
func printInt(w io.Writer, val int64, base int) {
w.Write([]byte(strconv.FormatInt(val, base)))
}
// printUint outputs an unsigned integer value to Writer w.
func printUint(w io.Writer, val uint64, base int) {
w.Write([]byte(strconv.FormatUint(val, base)))
}
// printFloat outputs a floating point value using the specified precision,
// which is expected to be 32 or 64bit, to Writer w.
func printFloat(w io.Writer, val float64, precision int) {
w.Write([]byte(strconv.FormatFloat(val, 'g', -1, precision)))
}
// printComplex outputs a complex value using the specified float precision
// for the real and imaginary parts to Writer w.
func printComplex(w io.Writer, c complex128, floatPrecision int) {
r := real(c)
w.Write(openParenBytes)
w.Write([]byte(strconv.FormatFloat(r, 'g', -1, floatPrecision)))
i := imag(c)
if i >= 0 {
w.Write(plusBytes)
}
w.Write([]byte(strconv.FormatFloat(i, 'g', -1, floatPrecision)))
w.Write(iBytes)
w.Write(closeParenBytes)
}
// printHexPtr outputs a uintptr formatted as hexidecimal with a leading '0x'
// prefix to Writer w.
func printHexPtr(w io.Writer, p uintptr) {
// Null pointer.
num := uint64(p)
if num == 0 {
w.Write(nilAngleBytes)
return
}
// Max uint64 is 16 bytes in hex + 2 bytes for '0x' prefix
buf := make([]byte, 18)
// It's simpler to construct the hex string right to left.
base := uint64(16)
i := len(buf) - 1
for num >= base {
buf[i] = hexDigits[num%base]
num /= base
i--
}
buf[i] = hexDigits[num]
// Add '0x' prefix.
i--
buf[i] = 'x'
i--
buf[i] = '0'
// Strip unused leading bytes.
buf = buf[i:]
w.Write(buf)
}
// valuesSorter implements sort.Interface to allow a slice of reflect.Value
// elements to be sorted.
type valuesSorter struct {
values []reflect.Value
strings []string // either nil or same len and values
cs *ConfigState
}
// newValuesSorter initializes a valuesSorter instance, which holds a set of
// surrogate keys on which the data should be sorted. It uses flags in
// ConfigState to decide if and how to populate those surrogate keys.
func newValuesSorter(values []reflect.Value, cs *ConfigState) sort.Interface {
vs := &valuesSorter{values: values, cs: cs}
if canSortSimply(vs.values[0].Kind()) {
return vs
}
if !cs.DisableMethods {
vs.strings = make([]string, len(values))
for i := range vs.values {
b := bytes.Buffer{}
if !handleMethods(cs, &b, vs.values[i]) {
vs.strings = nil
break
}
vs.strings[i] = b.String()
}
}
if vs.strings == nil && cs.SpewKeys {
vs.strings = make([]string, len(values))
for i := range vs.values {
vs.strings[i] = Sprintf("%#v", vs.values[i].Interface())
}
}
return vs
}
// canSortSimply tests whether a reflect.Kind is a primitive that can be sorted
// directly, or whether it should be considered for sorting by surrogate keys
// (if the ConfigState allows it).
func canSortSimply(kind reflect.Kind) bool {
// This switch parallels valueSortLess, except for the default case.
switch kind {
case reflect.Bool:
return true
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
return true
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
return true
case reflect.Float32, reflect.Float64:
return true
case reflect.String:
return true
case reflect.Uintptr:
return true
case reflect.Array:
return true
}
return false
}
// Len returns the number of values in the slice. It is part of the
// sort.Interface implementation.
func (s *valuesSorter) Len() int {
return len(s.values)
}
// Swap swaps the values at the passed indices. It is part of the
// sort.Interface implementation.
func (s *valuesSorter) Swap(i, j int) {
s.values[i], s.values[j] = s.values[j], s.values[i]
if s.strings != nil {
s.strings[i], s.strings[j] = s.strings[j], s.strings[i]
}
}
// valueSortLess returns whether the first value should sort before the second
// value. It is used by valueSorter.Less as part of the sort.Interface
// implementation.
func valueSortLess(a, b reflect.Value) bool {
switch a.Kind() {
case reflect.Bool:
return !a.Bool() && b.Bool()
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
return a.Int() < b.Int()
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
return a.Uint() < b.Uint()
case reflect.Float32, reflect.Float64:
return a.Float() < b.Float()
case reflect.String:
return a.String() < b.String()
case reflect.Uintptr:
return a.Uint() < b.Uint()
case reflect.Array:
// Compare the contents of both arrays.
l := a.Len()
for i := 0; i < l; i++ {
av := a.Index(i)
bv := b.Index(i)
if av.Interface() == bv.Interface() {
continue
}
return valueSortLess(av, bv)
}
}
return a.String() < b.String()
}
// Less returns whether the value at index i should sort before the
// value at index j. It is part of the sort.Interface implementation.
func (s *valuesSorter) Less(i, j int) bool {
if s.strings == nil {
return valueSortLess(s.values[i], s.values[j])
}
return s.strings[i] < s.strings[j]
}
// sortValues is a sort function that handles both native types and any type that
// can be converted to error or Stringer. Other inputs are sorted according to
// their Value.String() value to ensure display stability.
func sortValues(values []reflect.Value, cs *ConfigState) {
if len(values) == 0 {
return
}
sort.Sort(newValuesSorter(values, cs))
}

View File

@ -1,306 +0,0 @@
/*
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
*
* 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.
*/
package spew
import (
"bytes"
"fmt"
"io"
"os"
)
// ConfigState houses the configuration options used by spew to format and
// display values. There is a global instance, Config, that is used to control
// all top-level Formatter and Dump functionality. Each ConfigState instance
// provides methods equivalent to the top-level functions.
//
// The zero value for ConfigState provides no indentation. You would typically
// want to set it to a space or a tab.
//
// Alternatively, you can use NewDefaultConfig to get a ConfigState instance
// with default settings. See the documentation of NewDefaultConfig for default
// values.
type ConfigState struct {
// Indent specifies the string to use for each indentation level. The
// global config instance that all top-level functions use set this to a
// single space by default. If you would like more indentation, you might
// set this to a tab with "\t" or perhaps two spaces with " ".
Indent string
// MaxDepth controls the maximum number of levels to descend into nested
// data structures. The default, 0, means there is no limit.
//
// NOTE: Circular data structures are properly detected, so it is not
// necessary to set this value unless you specifically want to limit deeply
// nested data structures.
MaxDepth int
// DisableMethods specifies whether or not error and Stringer interfaces are
// invoked for types that implement them.
DisableMethods bool
// DisablePointerMethods specifies whether or not to check for and invoke
// error and Stringer interfaces on types which only accept a pointer
// receiver when the current type is not a pointer.
//
// NOTE: This might be an unsafe action since calling one of these methods
// with a pointer receiver could technically mutate the value, however,
// in practice, types which choose to satisify an error or Stringer
// interface with a pointer receiver should not be mutating their state
// inside these interface methods. As a result, this option relies on
// access to the unsafe package, so it will not have any effect when
// running in environments without access to the unsafe package such as
// Google App Engine or with the "safe" build tag specified.
DisablePointerMethods bool
// DisablePointerAddresses specifies whether to disable the printing of
// pointer addresses. This is useful when diffing data structures in tests.
DisablePointerAddresses bool
// DisableCapacities specifies whether to disable the printing of capacities
// for arrays, slices, maps and channels. This is useful when diffing
// data structures in tests.
DisableCapacities bool
// ContinueOnMethod specifies whether or not recursion should continue once
// a custom error or Stringer interface is invoked. The default, false,
// means it will print the results of invoking the custom error or Stringer
// interface and return immediately instead of continuing to recurse into
// the internals of the data type.
//
// NOTE: This flag does not have any effect if method invocation is disabled
// via the DisableMethods or DisablePointerMethods options.
ContinueOnMethod bool
// SortKeys specifies map keys should be sorted before being printed. Use
// this to have a more deterministic, diffable output. Note that only
// native types (bool, int, uint, floats, uintptr and string) and types
// that support the error or Stringer interfaces (if methods are
// enabled) are supported, with other types sorted according to the
// reflect.Value.String() output which guarantees display stability.
SortKeys bool
// SpewKeys specifies that, as a last resort attempt, map keys should
// be spewed to strings and sorted by those strings. This is only
// considered if SortKeys is true.
SpewKeys bool
}
// Config is the active configuration of the top-level functions.
// The configuration can be changed by modifying the contents of spew.Config.
var Config = ConfigState{Indent: " "}
// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the formatted string as a value that satisfies error. See NewFormatter
// for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Errorf(format, c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Errorf(format string, a ...interface{}) (err error) {
return fmt.Errorf(format, c.convertArgs(a)...)
}
// Fprint is a wrapper for fmt.Fprint that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Fprint(w, c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Fprint(w io.Writer, a ...interface{}) (n int, err error) {
return fmt.Fprint(w, c.convertArgs(a)...)
}
// Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Fprintf(w, format, c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
return fmt.Fprintf(w, format, c.convertArgs(a)...)
}
// Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it
// passed with a Formatter interface returned by c.NewFormatter. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Fprintln(w, c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
return fmt.Fprintln(w, c.convertArgs(a)...)
}
// Print is a wrapper for fmt.Print that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Print(c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Print(a ...interface{}) (n int, err error) {
return fmt.Print(c.convertArgs(a)...)
}
// Printf is a wrapper for fmt.Printf that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Printf(format, c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Printf(format string, a ...interface{}) (n int, err error) {
return fmt.Printf(format, c.convertArgs(a)...)
}
// Println is a wrapper for fmt.Println that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Println(c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Println(a ...interface{}) (n int, err error) {
return fmt.Println(c.convertArgs(a)...)
}
// Sprint is a wrapper for fmt.Sprint that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the resulting string. See NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Sprint(c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Sprint(a ...interface{}) string {
return fmt.Sprint(c.convertArgs(a)...)
}
// Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the resulting string. See NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Sprintf(format, c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Sprintf(format string, a ...interface{}) string {
return fmt.Sprintf(format, c.convertArgs(a)...)
}
// Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it
// were passed with a Formatter interface returned by c.NewFormatter. It
// returns the resulting string. See NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Sprintln(c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Sprintln(a ...interface{}) string {
return fmt.Sprintln(c.convertArgs(a)...)
}
/*
NewFormatter returns a custom formatter that satisfies the fmt.Formatter
interface. As a result, it integrates cleanly with standard fmt package
printing functions. The formatter is useful for inline printing of smaller data
types similar to the standard %v format specifier.
The custom formatter only responds to the %v (most compact), %+v (adds pointer
addresses), %#v (adds types), and %#+v (adds types and pointer addresses) verb
combinations. Any other verbs such as %x and %q will be sent to the the
standard fmt package for formatting. In addition, the custom formatter ignores
the width and precision arguments (however they will still work on the format
specifiers not handled by the custom formatter).
Typically this function shouldn't be called directly. It is much easier to make
use of the custom formatter by calling one of the convenience functions such as
c.Printf, c.Println, or c.Printf.
*/
func (c *ConfigState) NewFormatter(v interface{}) fmt.Formatter {
return newFormatter(c, v)
}
// Fdump formats and displays the passed arguments to io.Writer w. It formats
// exactly the same as Dump.
func (c *ConfigState) Fdump(w io.Writer, a ...interface{}) {
fdump(c, w, a...)
}
/*
Dump displays the passed parameters to standard out with newlines, customizable
indentation, and additional debug information such as complete types and all
pointer addresses used to indirect to the final value. It provides the
following features over the built-in printing facilities provided by the fmt
package:
* Pointers are dereferenced and followed
* Circular data structures are detected and handled properly
* Custom Stringer/error interfaces are optionally invoked, including
on unexported types
* Custom types which only implement the Stringer/error interfaces via
a pointer receiver are optionally invoked when passing non-pointer
variables
* Byte arrays and slices are dumped like the hexdump -C command which
includes offsets, byte values in hex, and ASCII output
The configuration options are controlled by modifying the public members
of c. See ConfigState for options documentation.
See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to
get the formatted result as a string.
*/
func (c *ConfigState) Dump(a ...interface{}) {
fdump(c, os.Stdout, a...)
}
// Sdump returns a string with the passed arguments formatted exactly the same
// as Dump.
func (c *ConfigState) Sdump(a ...interface{}) string {
var buf bytes.Buffer
fdump(c, &buf, a...)
return buf.String()
}
// convertArgs accepts a slice of arguments and returns a slice of the same
// length with each argument converted to a spew Formatter interface using
// the ConfigState associated with s.
func (c *ConfigState) convertArgs(args []interface{}) (formatters []interface{}) {
formatters = make([]interface{}, len(args))
for index, arg := range args {
formatters[index] = newFormatter(c, arg)
}
return formatters
}
// NewDefaultConfig returns a ConfigState with the following default settings.
//
// Indent: " "
// MaxDepth: 0
// DisableMethods: false
// DisablePointerMethods: false
// ContinueOnMethod: false
// SortKeys: false
func NewDefaultConfig() *ConfigState {
return &ConfigState{Indent: " "}
}

View File

@ -1,211 +0,0 @@
/*
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
*
* 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.
*/
/*
Package spew implements a deep pretty printer for Go data structures to aid in
debugging.
A quick overview of the additional features spew provides over the built-in
printing facilities for Go data types are as follows:
* Pointers are dereferenced and followed
* Circular data structures are detected and handled properly
* Custom Stringer/error interfaces are optionally invoked, including
on unexported types
* Custom types which only implement the Stringer/error interfaces via
a pointer receiver are optionally invoked when passing non-pointer
variables
* Byte arrays and slices are dumped like the hexdump -C command which
includes offsets, byte values in hex, and ASCII output (only when using
Dump style)
There are two different approaches spew allows for dumping Go data structures:
* Dump style which prints with newlines, customizable indentation,
and additional debug information such as types and all pointer addresses
used to indirect to the final value
* A custom Formatter interface that integrates cleanly with the standard fmt
package and replaces %v, %+v, %#v, and %#+v to provide inline printing
similar to the default %v while providing the additional functionality
outlined above and passing unsupported format verbs such as %x and %q
along to fmt
Quick Start
This section demonstrates how to quickly get started with spew. See the
sections below for further details on formatting and configuration options.
To dump a variable with full newlines, indentation, type, and pointer
information use Dump, Fdump, or Sdump:
spew.Dump(myVar1, myVar2, ...)
spew.Fdump(someWriter, myVar1, myVar2, ...)
str := spew.Sdump(myVar1, myVar2, ...)
Alternatively, if you would prefer to use format strings with a compacted inline
printing style, use the convenience wrappers Printf, Fprintf, etc with
%v (most compact), %+v (adds pointer addresses), %#v (adds types), or
%#+v (adds types and pointer addresses):
spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
spew.Fprintf(someWriter, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
spew.Fprintf(someWriter, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
Configuration Options
Configuration of spew is handled by fields in the ConfigState type. For
convenience, all of the top-level functions use a global state available
via the spew.Config global.
It is also possible to create a ConfigState instance that provides methods
equivalent to the top-level functions. This allows concurrent configuration
options. See the ConfigState documentation for more details.
The following configuration options are available:
* Indent
String to use for each indentation level for Dump functions.
It is a single space by default. A popular alternative is "\t".
* MaxDepth
Maximum number of levels to descend into nested data structures.
There is no limit by default.
* DisableMethods
Disables invocation of error and Stringer interface methods.
Method invocation is enabled by default.
* DisablePointerMethods
Disables invocation of error and Stringer interface methods on types
which only accept pointer receivers from non-pointer variables.
Pointer method invocation is enabled by default.
* DisablePointerAddresses
DisablePointerAddresses specifies whether to disable the printing of
pointer addresses. This is useful when diffing data structures in tests.
* DisableCapacities
DisableCapacities specifies whether to disable the printing of
capacities for arrays, slices, maps and channels. This is useful when
diffing data structures in tests.
* ContinueOnMethod
Enables recursion into types after invoking error and Stringer interface
methods. Recursion after method invocation is disabled by default.
* SortKeys
Specifies map keys should be sorted before being printed. Use
this to have a more deterministic, diffable output. Note that
only native types (bool, int, uint, floats, uintptr and string)
and types which implement error or Stringer interfaces are
supported with other types sorted according to the
reflect.Value.String() output which guarantees display
stability. Natural map order is used by default.
* SpewKeys
Specifies that, as a last resort attempt, map keys should be
spewed to strings and sorted by those strings. This is only
considered if SortKeys is true.
Dump Usage
Simply call spew.Dump with a list of variables you want to dump:
spew.Dump(myVar1, myVar2, ...)
You may also call spew.Fdump if you would prefer to output to an arbitrary
io.Writer. For example, to dump to standard error:
spew.Fdump(os.Stderr, myVar1, myVar2, ...)
A third option is to call spew.Sdump to get the formatted output as a string:
str := spew.Sdump(myVar1, myVar2, ...)
Sample Dump Output
See the Dump example for details on the setup of the types and variables being
shown here.
(main.Foo) {
unexportedField: (*main.Bar)(0xf84002e210)({
flag: (main.Flag) flagTwo,
data: (uintptr) <nil>
}),
ExportedField: (map[interface {}]interface {}) (len=1) {
(string) (len=3) "one": (bool) true
}
}
Byte (and uint8) arrays and slices are displayed uniquely like the hexdump -C
command as shown.
([]uint8) (len=32 cap=32) {
00000000 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 |............... |
00000010 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 |!"#$%&'()*+,-./0|
00000020 31 32 |12|
}
Custom Formatter
Spew provides a custom formatter that implements the fmt.Formatter interface
so that it integrates cleanly with standard fmt package printing functions. The
formatter is useful for inline printing of smaller data types similar to the
standard %v format specifier.
The custom formatter only responds to the %v (most compact), %+v (adds pointer
addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb
combinations. Any other verbs such as %x and %q will be sent to the the
standard fmt package for formatting. In addition, the custom formatter ignores
the width and precision arguments (however they will still work on the format
specifiers not handled by the custom formatter).
Custom Formatter Usage
The simplest way to make use of the spew custom formatter is to call one of the
convenience functions such as spew.Printf, spew.Println, or spew.Printf. The
functions have syntax you are most likely already familiar with:
spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
spew.Println(myVar, myVar2)
spew.Fprintf(os.Stderr, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
spew.Fprintf(os.Stderr, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
See the Index for the full list convenience functions.
Sample Formatter Output
Double pointer to a uint8:
%v: <**>5
%+v: <**>(0xf8400420d0->0xf8400420c8)5
%#v: (**uint8)5
%#+v: (**uint8)(0xf8400420d0->0xf8400420c8)5
Pointer to circular struct with a uint8 field and a pointer to itself:
%v: <*>{1 <*><shown>}
%+v: <*>(0xf84003e260){ui8:1 c:<*>(0xf84003e260)<shown>}
%#v: (*main.circular){ui8:(uint8)1 c:(*main.circular)<shown>}
%#+v: (*main.circular)(0xf84003e260){ui8:(uint8)1 c:(*main.circular)(0xf84003e260)<shown>}
See the Printf example for details on the setup of variables being shown
here.
Errors
Since it is possible for custom Stringer/error interfaces to panic, spew
detects them and handles them internally by printing the panic information
inline with the output. Since spew is intended to provide deep pretty printing
capabilities on structures, it intentionally does not return any errors.
*/
package spew

View File

@ -1,509 +0,0 @@
/*
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
*
* 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.
*/
package spew
import (
"bytes"
"encoding/hex"
"fmt"
"io"
"os"
"reflect"
"regexp"
"strconv"
"strings"
)
var (
// uint8Type is a reflect.Type representing a uint8. It is used to
// convert cgo types to uint8 slices for hexdumping.
uint8Type = reflect.TypeOf(uint8(0))
// cCharRE is a regular expression that matches a cgo char.
// It is used to detect character arrays to hexdump them.
cCharRE = regexp.MustCompile("^.*\\._Ctype_char$")
// cUnsignedCharRE is a regular expression that matches a cgo unsigned
// char. It is used to detect unsigned character arrays to hexdump
// them.
cUnsignedCharRE = regexp.MustCompile("^.*\\._Ctype_unsignedchar$")
// cUint8tCharRE is a regular expression that matches a cgo uint8_t.
// It is used to detect uint8_t arrays to hexdump them.
cUint8tCharRE = regexp.MustCompile("^.*\\._Ctype_uint8_t$")
)
// dumpState contains information about the state of a dump operation.
type dumpState struct {
w io.Writer
depth int
pointers map[uintptr]int
ignoreNextType bool
ignoreNextIndent bool
cs *ConfigState
}
// indent performs indentation according to the depth level and cs.Indent
// option.
func (d *dumpState) indent() {
if d.ignoreNextIndent {
d.ignoreNextIndent = false
return
}
d.w.Write(bytes.Repeat([]byte(d.cs.Indent), d.depth))
}
// unpackValue returns values inside of non-nil interfaces when possible.
// This is useful for data types like structs, arrays, slices, and maps which
// can contain varying types packed inside an interface.
func (d *dumpState) unpackValue(v reflect.Value) reflect.Value {
if v.Kind() == reflect.Interface && !v.IsNil() {
v = v.Elem()
}
return v
}
// dumpPtr handles formatting of pointers by indirecting them as necessary.
func (d *dumpState) dumpPtr(v reflect.Value) {
// Remove pointers at or below the current depth from map used to detect
// circular refs.
for k, depth := range d.pointers {
if depth >= d.depth {
delete(d.pointers, k)
}
}
// Keep list of all dereferenced pointers to show later.
pointerChain := make([]uintptr, 0)
// Figure out how many levels of indirection there are by dereferencing
// pointers and unpacking interfaces down the chain while detecting circular
// references.
nilFound := false
cycleFound := false
indirects := 0
ve := v
for ve.Kind() == reflect.Ptr {
if ve.IsNil() {
nilFound = true
break
}
indirects++
addr := ve.Pointer()
pointerChain = append(pointerChain, addr)
if pd, ok := d.pointers[addr]; ok && pd < d.depth {
cycleFound = true
indirects--
break
}
d.pointers[addr] = d.depth
ve = ve.Elem()
if ve.Kind() == reflect.Interface {
if ve.IsNil() {
nilFound = true
break
}
ve = ve.Elem()
}
}
// Display type information.
d.w.Write(openParenBytes)
d.w.Write(bytes.Repeat(asteriskBytes, indirects))
d.w.Write([]byte(ve.Type().String()))
d.w.Write(closeParenBytes)
// Display pointer information.
if !d.cs.DisablePointerAddresses && len(pointerChain) > 0 {
d.w.Write(openParenBytes)
for i, addr := range pointerChain {
if i > 0 {
d.w.Write(pointerChainBytes)
}
printHexPtr(d.w, addr)
}
d.w.Write(closeParenBytes)
}
// Display dereferenced value.
d.w.Write(openParenBytes)
switch {
case nilFound == true:
d.w.Write(nilAngleBytes)
case cycleFound == true:
d.w.Write(circularBytes)
default:
d.ignoreNextType = true
d.dump(ve)
}
d.w.Write(closeParenBytes)
}
// dumpSlice handles formatting of arrays and slices. Byte (uint8 under
// reflection) arrays and slices are dumped in hexdump -C fashion.
func (d *dumpState) dumpSlice(v reflect.Value) {
// Determine whether this type should be hex dumped or not. Also,
// for types which should be hexdumped, try to use the underlying data
// first, then fall back to trying to convert them to a uint8 slice.
var buf []uint8
doConvert := false
doHexDump := false
numEntries := v.Len()
if numEntries > 0 {
vt := v.Index(0).Type()
vts := vt.String()
switch {
// C types that need to be converted.
case cCharRE.MatchString(vts):
fallthrough
case cUnsignedCharRE.MatchString(vts):
fallthrough
case cUint8tCharRE.MatchString(vts):
doConvert = true
// Try to use existing uint8 slices and fall back to converting
// and copying if that fails.
case vt.Kind() == reflect.Uint8:
// We need an addressable interface to convert the type
// to a byte slice. However, the reflect package won't
// give us an interface on certain things like
// unexported struct fields in order to enforce
// visibility rules. We use unsafe, when available, to
// bypass these restrictions since this package does not
// mutate the values.
vs := v
if !vs.CanInterface() || !vs.CanAddr() {
vs = unsafeReflectValue(vs)
}
if !UnsafeDisabled {
vs = vs.Slice(0, numEntries)
// Use the existing uint8 slice if it can be
// type asserted.
iface := vs.Interface()
if slice, ok := iface.([]uint8); ok {
buf = slice
doHexDump = true
break
}
}
// The underlying data needs to be converted if it can't
// be type asserted to a uint8 slice.
doConvert = true
}
// Copy and convert the underlying type if needed.
if doConvert && vt.ConvertibleTo(uint8Type) {
// Convert and copy each element into a uint8 byte
// slice.
buf = make([]uint8, numEntries)
for i := 0; i < numEntries; i++ {
vv := v.Index(i)
buf[i] = uint8(vv.Convert(uint8Type).Uint())
}
doHexDump = true
}
}
// Hexdump the entire slice as needed.
if doHexDump {
indent := strings.Repeat(d.cs.Indent, d.depth)
str := indent + hex.Dump(buf)
str = strings.Replace(str, "\n", "\n"+indent, -1)
str = strings.TrimRight(str, d.cs.Indent)
d.w.Write([]byte(str))
return
}
// Recursively call dump for each item.
for i := 0; i < numEntries; i++ {
d.dump(d.unpackValue(v.Index(i)))
if i < (numEntries - 1) {
d.w.Write(commaNewlineBytes)
} else {
d.w.Write(newlineBytes)
}
}
}
// dump is the main workhorse for dumping a value. It uses the passed reflect
// value to figure out what kind of object we are dealing with and formats it
// appropriately. It is a recursive function, however circular data structures
// are detected and handled properly.
func (d *dumpState) dump(v reflect.Value) {
// Handle invalid reflect values immediately.
kind := v.Kind()
if kind == reflect.Invalid {
d.w.Write(invalidAngleBytes)
return
}
// Handle pointers specially.
if kind == reflect.Ptr {
d.indent()
d.dumpPtr(v)
return
}
// Print type information unless already handled elsewhere.
if !d.ignoreNextType {
d.indent()
d.w.Write(openParenBytes)
d.w.Write([]byte(v.Type().String()))
d.w.Write(closeParenBytes)
d.w.Write(spaceBytes)
}
d.ignoreNextType = false
// Display length and capacity if the built-in len and cap functions
// work with the value's kind and the len/cap itself is non-zero.
valueLen, valueCap := 0, 0
switch v.Kind() {
case reflect.Array, reflect.Slice, reflect.Chan:
valueLen, valueCap = v.Len(), v.Cap()
case reflect.Map, reflect.String:
valueLen = v.Len()
}
if valueLen != 0 || !d.cs.DisableCapacities && valueCap != 0 {
d.w.Write(openParenBytes)
if valueLen != 0 {
d.w.Write(lenEqualsBytes)
printInt(d.w, int64(valueLen), 10)
}
if !d.cs.DisableCapacities && valueCap != 0 {
if valueLen != 0 {
d.w.Write(spaceBytes)
}
d.w.Write(capEqualsBytes)
printInt(d.w, int64(valueCap), 10)
}
d.w.Write(closeParenBytes)
d.w.Write(spaceBytes)
}
// Call Stringer/error interfaces if they exist and the handle methods flag
// is enabled
if !d.cs.DisableMethods {
if (kind != reflect.Invalid) && (kind != reflect.Interface) {
if handled := handleMethods(d.cs, d.w, v); handled {
return
}
}
}
switch kind {
case reflect.Invalid:
// Do nothing. We should never get here since invalid has already
// been handled above.
case reflect.Bool:
printBool(d.w, v.Bool())
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
printInt(d.w, v.Int(), 10)
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
printUint(d.w, v.Uint(), 10)
case reflect.Float32:
printFloat(d.w, v.Float(), 32)
case reflect.Float64:
printFloat(d.w, v.Float(), 64)
case reflect.Complex64:
printComplex(d.w, v.Complex(), 32)
case reflect.Complex128:
printComplex(d.w, v.Complex(), 64)
case reflect.Slice:
if v.IsNil() {
d.w.Write(nilAngleBytes)
break
}
fallthrough
case reflect.Array:
d.w.Write(openBraceNewlineBytes)
d.depth++
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
d.indent()
d.w.Write(maxNewlineBytes)
} else {
d.dumpSlice(v)
}
d.depth--
d.indent()
d.w.Write(closeBraceBytes)
case reflect.String:
d.w.Write([]byte(strconv.Quote(v.String())))
case reflect.Interface:
// The only time we should get here is for nil interfaces due to
// unpackValue calls.
if v.IsNil() {
d.w.Write(nilAngleBytes)
}
case reflect.Ptr:
// Do nothing. We should never get here since pointers have already
// been handled above.
case reflect.Map:
// nil maps should be indicated as different than empty maps
if v.IsNil() {
d.w.Write(nilAngleBytes)
break
}
d.w.Write(openBraceNewlineBytes)
d.depth++
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
d.indent()
d.w.Write(maxNewlineBytes)
} else {
numEntries := v.Len()
keys := v.MapKeys()
if d.cs.SortKeys {
sortValues(keys, d.cs)
}
for i, key := range keys {
d.dump(d.unpackValue(key))
d.w.Write(colonSpaceBytes)
d.ignoreNextIndent = true
d.dump(d.unpackValue(v.MapIndex(key)))
if i < (numEntries - 1) {
d.w.Write(commaNewlineBytes)
} else {
d.w.Write(newlineBytes)
}
}
}
d.depth--
d.indent()
d.w.Write(closeBraceBytes)
case reflect.Struct:
d.w.Write(openBraceNewlineBytes)
d.depth++
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
d.indent()
d.w.Write(maxNewlineBytes)
} else {
vt := v.Type()
numFields := v.NumField()
for i := 0; i < numFields; i++ {
d.indent()
vtf := vt.Field(i)
d.w.Write([]byte(vtf.Name))
d.w.Write(colonSpaceBytes)
d.ignoreNextIndent = true
d.dump(d.unpackValue(v.Field(i)))
if i < (numFields - 1) {
d.w.Write(commaNewlineBytes)
} else {
d.w.Write(newlineBytes)
}
}
}
d.depth--
d.indent()
d.w.Write(closeBraceBytes)
case reflect.Uintptr:
printHexPtr(d.w, uintptr(v.Uint()))
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
printHexPtr(d.w, v.Pointer())
// There were not any other types at the time this code was written, but
// fall back to letting the default fmt package handle it in case any new
// types are added.
default:
if v.CanInterface() {
fmt.Fprintf(d.w, "%v", v.Interface())
} else {
fmt.Fprintf(d.w, "%v", v.String())
}
}
}
// fdump is a helper function to consolidate the logic from the various public
// methods which take varying writers and config states.
func fdump(cs *ConfigState, w io.Writer, a ...interface{}) {
for _, arg := range a {
if arg == nil {
w.Write(interfaceBytes)
w.Write(spaceBytes)
w.Write(nilAngleBytes)
w.Write(newlineBytes)
continue
}
d := dumpState{w: w, cs: cs}
d.pointers = make(map[uintptr]int)
d.dump(reflect.ValueOf(arg))
d.w.Write(newlineBytes)
}
}
// Fdump formats and displays the passed arguments to io.Writer w. It formats
// exactly the same as Dump.
func Fdump(w io.Writer, a ...interface{}) {
fdump(&Config, w, a...)
}
// Sdump returns a string with the passed arguments formatted exactly the same
// as Dump.
func Sdump(a ...interface{}) string {
var buf bytes.Buffer
fdump(&Config, &buf, a...)
return buf.String()
}
/*
Dump displays the passed parameters to standard out with newlines, customizable
indentation, and additional debug information such as complete types and all
pointer addresses used to indirect to the final value. It provides the
following features over the built-in printing facilities provided by the fmt
package:
* Pointers are dereferenced and followed
* Circular data structures are detected and handled properly
* Custom Stringer/error interfaces are optionally invoked, including
on unexported types
* Custom types which only implement the Stringer/error interfaces via
a pointer receiver are optionally invoked when passing non-pointer
variables
* Byte arrays and slices are dumped like the hexdump -C command which
includes offsets, byte values in hex, and ASCII output
The configuration options are controlled by an exported package global,
spew.Config. See ConfigState for options documentation.
See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to
get the formatted result as a string.
*/
func Dump(a ...interface{}) {
fdump(&Config, os.Stdout, a...)
}

View File

@ -1,419 +0,0 @@
/*
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
*
* 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.
*/
package spew
import (
"bytes"
"fmt"
"reflect"
"strconv"
"strings"
)
// supportedFlags is a list of all the character flags supported by fmt package.
const supportedFlags = "0-+# "
// formatState implements the fmt.Formatter interface and contains information
// about the state of a formatting operation. The NewFormatter function can
// be used to get a new Formatter which can be used directly as arguments
// in standard fmt package printing calls.
type formatState struct {
value interface{}
fs fmt.State
depth int
pointers map[uintptr]int
ignoreNextType bool
cs *ConfigState
}
// buildDefaultFormat recreates the original format string without precision
// and width information to pass in to fmt.Sprintf in the case of an
// unrecognized type. Unless new types are added to the language, this
// function won't ever be called.
func (f *formatState) buildDefaultFormat() (format string) {
buf := bytes.NewBuffer(percentBytes)
for _, flag := range supportedFlags {
if f.fs.Flag(int(flag)) {
buf.WriteRune(flag)
}
}
buf.WriteRune('v')
format = buf.String()
return format
}
// constructOrigFormat recreates the original format string including precision
// and width information to pass along to the standard fmt package. This allows
// automatic deferral of all format strings this package doesn't support.
func (f *formatState) constructOrigFormat(verb rune) (format string) {
buf := bytes.NewBuffer(percentBytes)
for _, flag := range supportedFlags {
if f.fs.Flag(int(flag)) {
buf.WriteRune(flag)
}
}
if width, ok := f.fs.Width(); ok {
buf.WriteString(strconv.Itoa(width))
}
if precision, ok := f.fs.Precision(); ok {
buf.Write(precisionBytes)
buf.WriteString(strconv.Itoa(precision))
}
buf.WriteRune(verb)
format = buf.String()
return format
}
// unpackValue returns values inside of non-nil interfaces when possible and
// ensures that types for values which have been unpacked from an interface
// are displayed when the show types flag is also set.
// This is useful for data types like structs, arrays, slices, and maps which
// can contain varying types packed inside an interface.
func (f *formatState) unpackValue(v reflect.Value) reflect.Value {
if v.Kind() == reflect.Interface {
f.ignoreNextType = false
if !v.IsNil() {
v = v.Elem()
}
}
return v
}
// formatPtr handles formatting of pointers by indirecting them as necessary.
func (f *formatState) formatPtr(v reflect.Value) {
// Display nil if top level pointer is nil.
showTypes := f.fs.Flag('#')
if v.IsNil() && (!showTypes || f.ignoreNextType) {
f.fs.Write(nilAngleBytes)
return
}
// Remove pointers at or below the current depth from map used to detect
// circular refs.
for k, depth := range f.pointers {
if depth >= f.depth {
delete(f.pointers, k)
}
}
// Keep list of all dereferenced pointers to possibly show later.
pointerChain := make([]uintptr, 0)
// Figure out how many levels of indirection there are by derferencing
// pointers and unpacking interfaces down the chain while detecting circular
// references.
nilFound := false
cycleFound := false
indirects := 0
ve := v
for ve.Kind() == reflect.Ptr {
if ve.IsNil() {
nilFound = true
break
}
indirects++
addr := ve.Pointer()
pointerChain = append(pointerChain, addr)
if pd, ok := f.pointers[addr]; ok && pd < f.depth {
cycleFound = true
indirects--
break
}
f.pointers[addr] = f.depth
ve = ve.Elem()
if ve.Kind() == reflect.Interface {
if ve.IsNil() {
nilFound = true
break
}
ve = ve.Elem()
}
}
// Display type or indirection level depending on flags.
if showTypes && !f.ignoreNextType {
f.fs.Write(openParenBytes)
f.fs.Write(bytes.Repeat(asteriskBytes, indirects))
f.fs.Write([]byte(ve.Type().String()))
f.fs.Write(closeParenBytes)
} else {
if nilFound || cycleFound {
indirects += strings.Count(ve.Type().String(), "*")
}
f.fs.Write(openAngleBytes)
f.fs.Write([]byte(strings.Repeat("*", indirects)))
f.fs.Write(closeAngleBytes)
}
// Display pointer information depending on flags.
if f.fs.Flag('+') && (len(pointerChain) > 0) {
f.fs.Write(openParenBytes)
for i, addr := range pointerChain {
if i > 0 {
f.fs.Write(pointerChainBytes)
}
printHexPtr(f.fs, addr)
}
f.fs.Write(closeParenBytes)
}
// Display dereferenced value.
switch {
case nilFound == true:
f.fs.Write(nilAngleBytes)
case cycleFound == true:
f.fs.Write(circularShortBytes)
default:
f.ignoreNextType = true
f.format(ve)
}
}
// format is the main workhorse for providing the Formatter interface. It
// uses the passed reflect value to figure out what kind of object we are
// dealing with and formats it appropriately. It is a recursive function,
// however circular data structures are detected and handled properly.
func (f *formatState) format(v reflect.Value) {
// Handle invalid reflect values immediately.
kind := v.Kind()
if kind == reflect.Invalid {
f.fs.Write(invalidAngleBytes)
return
}
// Handle pointers specially.
if kind == reflect.Ptr {
f.formatPtr(v)
return
}
// Print type information unless already handled elsewhere.
if !f.ignoreNextType && f.fs.Flag('#') {
f.fs.Write(openParenBytes)
f.fs.Write([]byte(v.Type().String()))
f.fs.Write(closeParenBytes)
}
f.ignoreNextType = false
// Call Stringer/error interfaces if they exist and the handle methods
// flag is enabled.
if !f.cs.DisableMethods {
if (kind != reflect.Invalid) && (kind != reflect.Interface) {
if handled := handleMethods(f.cs, f.fs, v); handled {
return
}
}
}
switch kind {
case reflect.Invalid:
// Do nothing. We should never get here since invalid has already
// been handled above.
case reflect.Bool:
printBool(f.fs, v.Bool())
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
printInt(f.fs, v.Int(), 10)
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
printUint(f.fs, v.Uint(), 10)
case reflect.Float32:
printFloat(f.fs, v.Float(), 32)
case reflect.Float64:
printFloat(f.fs, v.Float(), 64)
case reflect.Complex64:
printComplex(f.fs, v.Complex(), 32)
case reflect.Complex128:
printComplex(f.fs, v.Complex(), 64)
case reflect.Slice:
if v.IsNil() {
f.fs.Write(nilAngleBytes)
break
}
fallthrough
case reflect.Array:
f.fs.Write(openBracketBytes)
f.depth++
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
f.fs.Write(maxShortBytes)
} else {
numEntries := v.Len()
for i := 0; i < numEntries; i++ {
if i > 0 {
f.fs.Write(spaceBytes)
}
f.ignoreNextType = true
f.format(f.unpackValue(v.Index(i)))
}
}
f.depth--
f.fs.Write(closeBracketBytes)
case reflect.String:
f.fs.Write([]byte(v.String()))
case reflect.Interface:
// The only time we should get here is for nil interfaces due to
// unpackValue calls.
if v.IsNil() {
f.fs.Write(nilAngleBytes)
}
case reflect.Ptr:
// Do nothing. We should never get here since pointers have already
// been handled above.
case reflect.Map:
// nil maps should be indicated as different than empty maps
if v.IsNil() {
f.fs.Write(nilAngleBytes)
break
}
f.fs.Write(openMapBytes)
f.depth++
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
f.fs.Write(maxShortBytes)
} else {
keys := v.MapKeys()
if f.cs.SortKeys {
sortValues(keys, f.cs)
}
for i, key := range keys {
if i > 0 {
f.fs.Write(spaceBytes)
}
f.ignoreNextType = true
f.format(f.unpackValue(key))
f.fs.Write(colonBytes)
f.ignoreNextType = true
f.format(f.unpackValue(v.MapIndex(key)))
}
}
f.depth--
f.fs.Write(closeMapBytes)
case reflect.Struct:
numFields := v.NumField()
f.fs.Write(openBraceBytes)
f.depth++
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
f.fs.Write(maxShortBytes)
} else {
vt := v.Type()
for i := 0; i < numFields; i++ {
if i > 0 {
f.fs.Write(spaceBytes)
}
vtf := vt.Field(i)
if f.fs.Flag('+') || f.fs.Flag('#') {
f.fs.Write([]byte(vtf.Name))
f.fs.Write(colonBytes)
}
f.format(f.unpackValue(v.Field(i)))
}
}
f.depth--
f.fs.Write(closeBraceBytes)
case reflect.Uintptr:
printHexPtr(f.fs, uintptr(v.Uint()))
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
printHexPtr(f.fs, v.Pointer())
// There were not any other types at the time this code was written, but
// fall back to letting the default fmt package handle it if any get added.
default:
format := f.buildDefaultFormat()
if v.CanInterface() {
fmt.Fprintf(f.fs, format, v.Interface())
} else {
fmt.Fprintf(f.fs, format, v.String())
}
}
}
// Format satisfies the fmt.Formatter interface. See NewFormatter for usage
// details.
func (f *formatState) Format(fs fmt.State, verb rune) {
f.fs = fs
// Use standard formatting for verbs that are not v.
if verb != 'v' {
format := f.constructOrigFormat(verb)
fmt.Fprintf(fs, format, f.value)
return
}
if f.value == nil {
if fs.Flag('#') {
fs.Write(interfaceBytes)
}
fs.Write(nilAngleBytes)
return
}
f.format(reflect.ValueOf(f.value))
}
// newFormatter is a helper function to consolidate the logic from the various
// public methods which take varying config states.
func newFormatter(cs *ConfigState, v interface{}) fmt.Formatter {
fs := &formatState{value: v, cs: cs}
fs.pointers = make(map[uintptr]int)
return fs
}
/*
NewFormatter returns a custom formatter that satisfies the fmt.Formatter
interface. As a result, it integrates cleanly with standard fmt package
printing functions. The formatter is useful for inline printing of smaller data
types similar to the standard %v format specifier.
The custom formatter only responds to the %v (most compact), %+v (adds pointer
addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb
combinations. Any other verbs such as %x and %q will be sent to the the
standard fmt package for formatting. In addition, the custom formatter ignores
the width and precision arguments (however they will still work on the format
specifiers not handled by the custom formatter).
Typically this function shouldn't be called directly. It is much easier to make
use of the custom formatter by calling one of the convenience functions such as
Printf, Println, or Fprintf.
*/
func NewFormatter(v interface{}) fmt.Formatter {
return newFormatter(&Config, v)
}

View File

@ -1,148 +0,0 @@
/*
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
*
* 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.
*/
package spew
import (
"fmt"
"io"
)
// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the formatted string as a value that satisfies error. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Errorf(format, spew.NewFormatter(a), spew.NewFormatter(b))
func Errorf(format string, a ...interface{}) (err error) {
return fmt.Errorf(format, convertArgs(a)...)
}
// Fprint is a wrapper for fmt.Fprint that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Fprint(w, spew.NewFormatter(a), spew.NewFormatter(b))
func Fprint(w io.Writer, a ...interface{}) (n int, err error) {
return fmt.Fprint(w, convertArgs(a)...)
}
// Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Fprintf(w, format, spew.NewFormatter(a), spew.NewFormatter(b))
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
return fmt.Fprintf(w, format, convertArgs(a)...)
}
// Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it
// passed with a default Formatter interface returned by NewFormatter. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Fprintln(w, spew.NewFormatter(a), spew.NewFormatter(b))
func Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
return fmt.Fprintln(w, convertArgs(a)...)
}
// Print is a wrapper for fmt.Print that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Print(spew.NewFormatter(a), spew.NewFormatter(b))
func Print(a ...interface{}) (n int, err error) {
return fmt.Print(convertArgs(a)...)
}
// Printf is a wrapper for fmt.Printf that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Printf(format, spew.NewFormatter(a), spew.NewFormatter(b))
func Printf(format string, a ...interface{}) (n int, err error) {
return fmt.Printf(format, convertArgs(a)...)
}
// Println is a wrapper for fmt.Println that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Println(spew.NewFormatter(a), spew.NewFormatter(b))
func Println(a ...interface{}) (n int, err error) {
return fmt.Println(convertArgs(a)...)
}
// Sprint is a wrapper for fmt.Sprint that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the resulting string. See NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Sprint(spew.NewFormatter(a), spew.NewFormatter(b))
func Sprint(a ...interface{}) string {
return fmt.Sprint(convertArgs(a)...)
}
// Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the resulting string. See NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Sprintf(format, spew.NewFormatter(a), spew.NewFormatter(b))
func Sprintf(format string, a ...interface{}) string {
return fmt.Sprintf(format, convertArgs(a)...)
}
// Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it
// were passed with a default Formatter interface returned by NewFormatter. It
// returns the resulting string. See NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Sprintln(spew.NewFormatter(a), spew.NewFormatter(b))
func Sprintln(a ...interface{}) string {
return fmt.Sprintln(convertArgs(a)...)
}
// convertArgs accepts a slice of arguments and returns a slice of the same
// length with each argument converted to a default spew Formatter interface.
func convertArgs(args []interface{}) (formatters []interface{}) {
formatters = make([]interface{}, len(args))
for index, arg := range args {
formatters[index] = NewFormatter(arg)
}
return formatters
}

View File

@ -1,21 +0,0 @@
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 ./...

View File

@ -1,21 +0,0 @@
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>

View File

@ -1,124 +0,0 @@
# 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

View File

@ -1,31 +0,0 @@
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
}

View File

@ -1,173 +0,0 @@
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)
}

View File

@ -1,143 +0,0 @@
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)
}

View File

@ -1,116 +0,0 @@
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:], ",")
}

View File

@ -1,40 +0,0 @@
// +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()
}

View File

@ -1,46 +0,0 @@
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))
}

View File

@ -1,8 +0,0 @@
/*
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

View File

@ -1,192 +0,0 @@
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###,##" => "12345,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))
}

View File

@ -1,25 +0,0 @@
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
}

View File

@ -1,123 +0,0 @@
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
}

View File

@ -1,117 +0,0 @@
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...)
}

View File

@ -1,5 +0,0 @@
language: go
go:
- 1.8.x
- tip

View File

@ -1,27 +0,0 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
name = "github.com/mattn/go-colorable"
packages = ["."]
revision = "167de6bfdfba052fa6b2d3664c8f5272e23c9072"
version = "v0.0.9"
[[projects]]
name = "github.com/mattn/go-isatty"
packages = ["."]
revision = "0360b2af4f38e8d38c7fce2a9f4e702702d73a39"
version = "v0.0.3"
[[projects]]
branch = "master"
name = "golang.org/x/sys"
packages = ["unix"]
revision = "37707fdb30a5b38865cfb95e5aab41707daec7fd"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "e8a50671c3cb93ea935bf210b1cd20702876b9d9226129be581ef646d1565cdc"
solver-name = "gps-cdcl"
solver-version = 1

View File

@ -1,30 +0,0 @@
# Gopkg.toml example
#
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
# name = "github.com/user/project"
# version = "1.0.0"
#
# [[constraint]]
# name = "github.com/user/project2"
# branch = "dev"
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"
[[constraint]]
name = "github.com/mattn/go-colorable"
version = "0.0.9"
[[constraint]]
name = "github.com/mattn/go-isatty"
version = "0.0.3"

View File

@ -1,20 +0,0 @@
The MIT License (MIT)
Copyright (c) 2013 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.

View File

@ -1,179 +0,0 @@
# Color [![GoDoc](https://godoc.org/github.com/fatih/color?status.svg)](https://godoc.org/github.com/fatih/color) [![Build Status](https://img.shields.io/travis/fatih/color.svg?style=flat-square)](https://travis-ci.org/fatih/color)
Color lets you use colorized outputs in terms of [ANSI Escape
Codes](http://en.wikipedia.org/wiki/ANSI_escape_code#Colors) in Go (Golang). It
has support for Windows too! The API can be used in several ways, pick one that
suits you.
![Color](https://i.imgur.com/c1JI0lA.png)
## Install
```bash
go get github.com/fatih/color
```
Note that the `vendor` folder is here for stability. Remove the folder if you
already have the dependencies in your GOPATH.
## Examples
### Standard colors
```go
// Print with default helper functions
color.Cyan("Prints text in cyan.")
// A newline will be appended automatically
color.Blue("Prints %s in blue.", "text")
// These are using the default foreground colors
color.Red("We have red")
color.Magenta("And many others ..")
```
### Mix and reuse colors
```go
// Create a new color object
c := color.New(color.FgCyan).Add(color.Underline)
c.Println("Prints cyan text with an underline.")
// Or just add them to New()
d := color.New(color.FgCyan, color.Bold)
d.Printf("This prints bold cyan %s\n", "too!.")
// Mix up foreground and background colors, create new mixes!
red := color.New(color.FgRed)
boldRed := red.Add(color.Bold)
boldRed.Println("This will print text in bold red.")
whiteBackground := red.Add(color.BgWhite)
whiteBackground.Println("Red text with white background.")
```
### Use your own output (io.Writer)
```go
// Use your own io.Writer output
color.New(color.FgBlue).Fprintln(myWriter, "blue color!")
blue := color.New(color.FgBlue)
blue.Fprint(writer, "This will print text in blue.")
```
### Custom print functions (PrintFunc)
```go
// Create a custom print function for convenience
red := color.New(color.FgRed).PrintfFunc()
red("Warning")
red("Error: %s", err)
// Mix up multiple attributes
notice := color.New(color.Bold, color.FgGreen).PrintlnFunc()
notice("Don't forget this...")
```
### Custom fprint functions (FprintFunc)
```go
blue := color.New(FgBlue).FprintfFunc()
blue(myWriter, "important notice: %s", stars)
// Mix up with multiple attributes
success := color.New(color.Bold, color.FgGreen).FprintlnFunc()
success(myWriter, "Don't forget this...")
```
### Insert into noncolor strings (SprintFunc)
```go
// Create SprintXxx functions to mix strings with other non-colorized strings:
yellow := color.New(color.FgYellow).SprintFunc()
red := color.New(color.FgRed).SprintFunc()
fmt.Printf("This is a %s and this is %s.\n", yellow("warning"), red("error"))
info := color.New(color.FgWhite, color.BgGreen).SprintFunc()
fmt.Printf("This %s rocks!\n", info("package"))
// Use helper functions
fmt.Println("This", color.RedString("warning"), "should be not neglected.")
fmt.Printf("%v %v\n", color.GreenString("Info:"), "an important message.")
// Windows supported too! Just don't forget to change the output to color.Output
fmt.Fprintf(color.Output, "Windows support: %s", color.GreenString("PASS"))
```
### Plug into existing code
```go
// Use handy standard colors
color.Set(color.FgYellow)
fmt.Println("Existing text will now be in yellow")
fmt.Printf("This one %s\n", "too")
color.Unset() // Don't forget to unset
// You can mix up parameters
color.Set(color.FgMagenta, color.Bold)
defer color.Unset() // Use it in your function
fmt.Println("All text will now be bold magenta.")
```
### Disable/Enable color
There might be a case where you want to explicitly disable/enable color output. the
`go-isatty` package will automatically disable color output for non-tty output streams
(for example if the output were piped directly to `less`)
`Color` has support to disable/enable colors both globally and for single color
definitions. For example suppose you have a CLI app and a `--no-color` bool flag. You
can easily disable the color output with:
```go
var flagNoColor = flag.Bool("no-color", false, "Disable color output")
if *flagNoColor {
color.NoColor = true // disables colorized output
}
```
It also has support for single color definitions (local). You can
disable/enable color output on the fly:
```go
c := color.New(color.FgCyan)
c.Println("Prints cyan text")
c.DisableColor()
c.Println("This is printed without any color")
c.EnableColor()
c.Println("This prints again cyan...")
```
## Todo
* Save/Return previous values
* Evaluate fmt.Formatter interface
## Credits
* [Fatih Arslan](https://github.com/fatih)
* Windows support via @mattn: [colorable](https://github.com/mattn/go-colorable)
## License
The MIT License (MIT) - see [`LICENSE.md`](https://github.com/fatih/color/blob/master/LICENSE.md) for more details

View File

@ -1,603 +0,0 @@
package color
import (
"fmt"
"io"
"os"
"strconv"
"strings"
"sync"
"github.com/mattn/go-colorable"
"github.com/mattn/go-isatty"
)
var (
// NoColor defines if the output is colorized or not. It's dynamically set to
// false or true based on the stdout's file descriptor referring to a terminal
// or not. This is a global option and affects all colors. For more control
// over each color block use the methods DisableColor() individually.
NoColor = os.Getenv("TERM") == "dumb" ||
(!isatty.IsTerminal(os.Stdout.Fd()) && !isatty.IsCygwinTerminal(os.Stdout.Fd()))
// Output defines the standard output of the print functions. By default
// os.Stdout is used.
Output = colorable.NewColorableStdout()
// Error defines a color supporting writer for os.Stderr.
Error = colorable.NewColorableStderr()
// colorsCache is used to reduce the count of created Color objects and
// allows to reuse already created objects with required Attribute.
colorsCache = make(map[Attribute]*Color)
colorsCacheMu sync.Mutex // protects colorsCache
)
// Color defines a custom color object which is defined by SGR parameters.
type Color struct {
params []Attribute
noColor *bool
}
// Attribute defines a single SGR Code
type Attribute int
const escape = "\x1b"
// Base attributes
const (
Reset Attribute = iota
Bold
Faint
Italic
Underline
BlinkSlow
BlinkRapid
ReverseVideo
Concealed
CrossedOut
)
// Foreground text colors
const (
FgBlack Attribute = iota + 30
FgRed
FgGreen
FgYellow
FgBlue
FgMagenta
FgCyan
FgWhite
)
// Foreground Hi-Intensity text colors
const (
FgHiBlack Attribute = iota + 90
FgHiRed
FgHiGreen
FgHiYellow
FgHiBlue
FgHiMagenta
FgHiCyan
FgHiWhite
)
// Background text colors
const (
BgBlack Attribute = iota + 40
BgRed
BgGreen
BgYellow
BgBlue
BgMagenta
BgCyan
BgWhite
)
// Background Hi-Intensity text colors
const (
BgHiBlack Attribute = iota + 100
BgHiRed
BgHiGreen
BgHiYellow
BgHiBlue
BgHiMagenta
BgHiCyan
BgHiWhite
)
// New returns a newly created color object.
func New(value ...Attribute) *Color {
c := &Color{params: make([]Attribute, 0)}
c.Add(value...)
return c
}
// Set sets the given parameters immediately. It will change the color of
// output with the given SGR parameters until color.Unset() is called.
func Set(p ...Attribute) *Color {
c := New(p...)
c.Set()
return c
}
// Unset resets all escape attributes and clears the output. Usually should
// be called after Set().
func Unset() {
if NoColor {
return
}
fmt.Fprintf(Output, "%s[%dm", escape, Reset)
}
// Set sets the SGR sequence.
func (c *Color) Set() *Color {
if c.isNoColorSet() {
return c
}
fmt.Fprintf(Output, c.format())
return c
}
func (c *Color) unset() {
if c.isNoColorSet() {
return
}
Unset()
}
func (c *Color) setWriter(w io.Writer) *Color {
if c.isNoColorSet() {
return c
}
fmt.Fprintf(w, c.format())
return c
}
func (c *Color) unsetWriter(w io.Writer) {
if c.isNoColorSet() {
return
}
if NoColor {
return
}
fmt.Fprintf(w, "%s[%dm", escape, Reset)
}
// Add is used to chain SGR parameters. Use as many as parameters to combine
// and create custom color objects. Example: Add(color.FgRed, color.Underline).
func (c *Color) Add(value ...Attribute) *Color {
c.params = append(c.params, value...)
return c
}
func (c *Color) prepend(value Attribute) {
c.params = append(c.params, 0)
copy(c.params[1:], c.params[0:])
c.params[0] = value
}
// Fprint formats using the default formats for its operands and writes to w.
// Spaces are added between operands when neither is a string.
// It returns the number of bytes written and any write error encountered.
// On Windows, users should wrap w with colorable.NewColorable() if w is of
// type *os.File.
func (c *Color) Fprint(w io.Writer, a ...interface{}) (n int, err error) {
c.setWriter(w)
defer c.unsetWriter(w)
return fmt.Fprint(w, a...)
}
// Print formats using the default formats for its operands and writes to
// standard output. Spaces are added between operands when neither is a
// string. It returns the number of bytes written and any write error
// encountered. This is the standard fmt.Print() method wrapped with the given
// color.
func (c *Color) Print(a ...interface{}) (n int, err error) {
c.Set()
defer c.unset()
return fmt.Fprint(Output, a...)
}
// Fprintf formats according to a format specifier and writes to w.
// It returns the number of bytes written and any write error encountered.
// On Windows, users should wrap w with colorable.NewColorable() if w is of
// type *os.File.
func (c *Color) Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
c.setWriter(w)
defer c.unsetWriter(w)
return fmt.Fprintf(w, format, a...)
}
// Printf formats according to a format specifier and writes to standard output.
// It returns the number of bytes written and any write error encountered.
// This is the standard fmt.Printf() method wrapped with the given color.
func (c *Color) Printf(format string, a ...interface{}) (n int, err error) {
c.Set()
defer c.unset()
return fmt.Fprintf(Output, format, a...)
}
// Fprintln formats using the default formats for its operands and writes to w.
// Spaces are always added between operands and a newline is appended.
// On Windows, users should wrap w with colorable.NewColorable() if w is of
// type *os.File.
func (c *Color) Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
c.setWriter(w)
defer c.unsetWriter(w)
return fmt.Fprintln(w, a...)
}
// Println formats using the default formats for its operands and writes to
// standard output. Spaces are always added between operands and a newline is
// appended. It returns the number of bytes written and any write error
// encountered. This is the standard fmt.Print() method wrapped with the given
// color.
func (c *Color) Println(a ...interface{}) (n int, err error) {
c.Set()
defer c.unset()
return fmt.Fprintln(Output, a...)
}
// Sprint is just like Print, but returns a string instead of printing it.
func (c *Color) Sprint(a ...interface{}) string {
return c.wrap(fmt.Sprint(a...))
}
// Sprintln is just like Println, but returns a string instead of printing it.
func (c *Color) Sprintln(a ...interface{}) string {
return c.wrap(fmt.Sprintln(a...))
}
// Sprintf is just like Printf, but returns a string instead of printing it.
func (c *Color) Sprintf(format string, a ...interface{}) string {
return c.wrap(fmt.Sprintf(format, a...))
}
// FprintFunc returns a new function that prints the passed arguments as
// colorized with color.Fprint().
func (c *Color) FprintFunc() func(w io.Writer, a ...interface{}) {
return func(w io.Writer, a ...interface{}) {
c.Fprint(w, a...)
}
}
// PrintFunc returns a new function that prints the passed arguments as
// colorized with color.Print().
func (c *Color) PrintFunc() func(a ...interface{}) {
return func(a ...interface{}) {
c.Print(a...)
}
}
// FprintfFunc returns a new function that prints the passed arguments as
// colorized with color.Fprintf().
func (c *Color) FprintfFunc() func(w io.Writer, format string, a ...interface{}) {
return func(w io.Writer, format string, a ...interface{}) {
c.Fprintf(w, format, a...)
}
}
// PrintfFunc returns a new function that prints the passed arguments as
// colorized with color.Printf().
func (c *Color) PrintfFunc() func(format string, a ...interface{}) {
return func(format string, a ...interface{}) {
c.Printf(format, a...)
}
}
// FprintlnFunc returns a new function that prints the passed arguments as
// colorized with color.Fprintln().
func (c *Color) FprintlnFunc() func(w io.Writer, a ...interface{}) {
return func(w io.Writer, a ...interface{}) {
c.Fprintln(w, a...)
}
}
// PrintlnFunc returns a new function that prints the passed arguments as
// colorized with color.Println().
func (c *Color) PrintlnFunc() func(a ...interface{}) {
return func(a ...interface{}) {
c.Println(a...)
}
}
// SprintFunc returns a new function that returns colorized strings for the
// given arguments with fmt.Sprint(). Useful to put into or mix into other
// string. Windows users should use this in conjunction with color.Output, example:
//
// put := New(FgYellow).SprintFunc()
// fmt.Fprintf(color.Output, "This is a %s", put("warning"))
func (c *Color) SprintFunc() func(a ...interface{}) string {
return func(a ...interface{}) string {
return c.wrap(fmt.Sprint(a...))
}
}
// SprintfFunc returns a new function that returns colorized strings for the
// given arguments with fmt.Sprintf(). Useful to put into or mix into other
// string. Windows users should use this in conjunction with color.Output.
func (c *Color) SprintfFunc() func(format string, a ...interface{}) string {
return func(format string, a ...interface{}) string {
return c.wrap(fmt.Sprintf(format, a...))
}
}
// SprintlnFunc returns a new function that returns colorized strings for the
// given arguments with fmt.Sprintln(). Useful to put into or mix into other
// string. Windows users should use this in conjunction with color.Output.
func (c *Color) SprintlnFunc() func(a ...interface{}) string {
return func(a ...interface{}) string {
return c.wrap(fmt.Sprintln(a...))
}
}
// sequence returns a formatted SGR sequence to be plugged into a "\x1b[...m"
// an example output might be: "1;36" -> bold cyan
func (c *Color) sequence() string {
format := make([]string, len(c.params))
for i, v := range c.params {
format[i] = strconv.Itoa(int(v))
}
return strings.Join(format, ";")
}
// wrap wraps the s string with the colors attributes. The string is ready to
// be printed.
func (c *Color) wrap(s string) string {
if c.isNoColorSet() {
return s
}
return c.format() + s + c.unformat()
}
func (c *Color) format() string {
return fmt.Sprintf("%s[%sm", escape, c.sequence())
}
func (c *Color) unformat() string {
return fmt.Sprintf("%s[%dm", escape, Reset)
}
// DisableColor disables the color output. Useful to not change any existing
// code and still being able to output. Can be used for flags like
// "--no-color". To enable back use EnableColor() method.
func (c *Color) DisableColor() {
c.noColor = boolPtr(true)
}
// EnableColor enables the color output. Use it in conjunction with
// DisableColor(). Otherwise this method has no side effects.
func (c *Color) EnableColor() {
c.noColor = boolPtr(false)
}
func (c *Color) isNoColorSet() bool {
// check first if we have user setted action
if c.noColor != nil {
return *c.noColor
}
// if not return the global option, which is disabled by default
return NoColor
}
// Equals returns a boolean value indicating whether two colors are equal.
func (c *Color) Equals(c2 *Color) bool {
if len(c.params) != len(c2.params) {
return false
}
for _, attr := range c.params {
if !c2.attrExists(attr) {
return false
}
}
return true
}
func (c *Color) attrExists(a Attribute) bool {
for _, attr := range c.params {
if attr == a {
return true
}
}
return false
}
func boolPtr(v bool) *bool {
return &v
}
func getCachedColor(p Attribute) *Color {
colorsCacheMu.Lock()
defer colorsCacheMu.Unlock()
c, ok := colorsCache[p]
if !ok {
c = New(p)
colorsCache[p] = c
}
return c
}
func colorPrint(format string, p Attribute, a ...interface{}) {
c := getCachedColor(p)
if !strings.HasSuffix(format, "\n") {
format += "\n"
}
if len(a) == 0 {
c.Print(format)
} else {
c.Printf(format, a...)
}
}
func colorString(format string, p Attribute, a ...interface{}) string {
c := getCachedColor(p)
if len(a) == 0 {
return c.SprintFunc()(format)
}
return c.SprintfFunc()(format, a...)
}
// Black is a convenient helper function to print with black foreground. A
// newline is appended to format by default.
func Black(format string, a ...interface{}) { colorPrint(format, FgBlack, a...) }
// Red is a convenient helper function to print with red foreground. A
// newline is appended to format by default.
func Red(format string, a ...interface{}) { colorPrint(format, FgRed, a...) }
// Green is a convenient helper function to print with green foreground. A
// newline is appended to format by default.
func Green(format string, a ...interface{}) { colorPrint(format, FgGreen, a...) }
// Yellow is a convenient helper function to print with yellow foreground.
// A newline is appended to format by default.
func Yellow(format string, a ...interface{}) { colorPrint(format, FgYellow, a...) }
// Blue is a convenient helper function to print with blue foreground. A
// newline is appended to format by default.
func Blue(format string, a ...interface{}) { colorPrint(format, FgBlue, a...) }
// Magenta is a convenient helper function to print with magenta foreground.
// A newline is appended to format by default.
func Magenta(format string, a ...interface{}) { colorPrint(format, FgMagenta, a...) }
// Cyan is a convenient helper function to print with cyan foreground. A
// newline is appended to format by default.
func Cyan(format string, a ...interface{}) { colorPrint(format, FgCyan, a...) }
// White is a convenient helper function to print with white foreground. A
// newline is appended to format by default.
func White(format string, a ...interface{}) { colorPrint(format, FgWhite, a...) }
// BlackString is a convenient helper function to return a string with black
// foreground.
func BlackString(format string, a ...interface{}) string { return colorString(format, FgBlack, a...) }
// RedString is a convenient helper function to return a string with red
// foreground.
func RedString(format string, a ...interface{}) string { return colorString(format, FgRed, a...) }
// GreenString is a convenient helper function to return a string with green
// foreground.
func GreenString(format string, a ...interface{}) string { return colorString(format, FgGreen, a...) }
// YellowString is a convenient helper function to return a string with yellow
// foreground.
func YellowString(format string, a ...interface{}) string { return colorString(format, FgYellow, a...) }
// BlueString is a convenient helper function to return a string with blue
// foreground.
func BlueString(format string, a ...interface{}) string { return colorString(format, FgBlue, a...) }
// MagentaString is a convenient helper function to return a string with magenta
// foreground.
func MagentaString(format string, a ...interface{}) string {
return colorString(format, FgMagenta, a...)
}
// CyanString is a convenient helper function to return a string with cyan
// foreground.
func CyanString(format string, a ...interface{}) string { return colorString(format, FgCyan, a...) }
// WhiteString is a convenient helper function to return a string with white
// foreground.
func WhiteString(format string, a ...interface{}) string { return colorString(format, FgWhite, a...) }
// HiBlack is a convenient helper function to print with hi-intensity black foreground. A
// newline is appended to format by default.
func HiBlack(format string, a ...interface{}) { colorPrint(format, FgHiBlack, a...) }
// HiRed is a convenient helper function to print with hi-intensity red foreground. A
// newline is appended to format by default.
func HiRed(format string, a ...interface{}) { colorPrint(format, FgHiRed, a...) }
// HiGreen is a convenient helper function to print with hi-intensity green foreground. A
// newline is appended to format by default.
func HiGreen(format string, a ...interface{}) { colorPrint(format, FgHiGreen, a...) }
// HiYellow is a convenient helper function to print with hi-intensity yellow foreground.
// A newline is appended to format by default.
func HiYellow(format string, a ...interface{}) { colorPrint(format, FgHiYellow, a...) }
// HiBlue is a convenient helper function to print with hi-intensity blue foreground. A
// newline is appended to format by default.
func HiBlue(format string, a ...interface{}) { colorPrint(format, FgHiBlue, a...) }
// HiMagenta is a convenient helper function to print with hi-intensity magenta foreground.
// A newline is appended to format by default.
func HiMagenta(format string, a ...interface{}) { colorPrint(format, FgHiMagenta, a...) }
// HiCyan is a convenient helper function to print with hi-intensity cyan foreground. A
// newline is appended to format by default.
func HiCyan(format string, a ...interface{}) { colorPrint(format, FgHiCyan, a...) }
// HiWhite is a convenient helper function to print with hi-intensity white foreground. A
// newline is appended to format by default.
func HiWhite(format string, a ...interface{}) { colorPrint(format, FgHiWhite, a...) }
// HiBlackString is a convenient helper function to return a string with hi-intensity black
// foreground.
func HiBlackString(format string, a ...interface{}) string {
return colorString(format, FgHiBlack, a...)
}
// HiRedString is a convenient helper function to return a string with hi-intensity red
// foreground.
func HiRedString(format string, a ...interface{}) string { return colorString(format, FgHiRed, a...) }
// HiGreenString is a convenient helper function to return a string with hi-intensity green
// foreground.
func HiGreenString(format string, a ...interface{}) string {
return colorString(format, FgHiGreen, a...)
}
// HiYellowString is a convenient helper function to return a string with hi-intensity yellow
// foreground.
func HiYellowString(format string, a ...interface{}) string {
return colorString(format, FgHiYellow, a...)
}
// HiBlueString is a convenient helper function to return a string with hi-intensity blue
// foreground.
func HiBlueString(format string, a ...interface{}) string { return colorString(format, FgHiBlue, a...) }
// HiMagentaString is a convenient helper function to return a string with hi-intensity magenta
// foreground.
func HiMagentaString(format string, a ...interface{}) string {
return colorString(format, FgHiMagenta, a...)
}
// HiCyanString is a convenient helper function to return a string with hi-intensity cyan
// foreground.
func HiCyanString(format string, a ...interface{}) string { return colorString(format, FgHiCyan, a...) }
// HiWhiteString is a convenient helper function to return a string with hi-intensity white
// foreground.
func HiWhiteString(format string, a ...interface{}) string {
return colorString(format, FgHiWhite, a...)
}

133
vendor/github.com/fatih/color/doc.go generated vendored
View File

@ -1,133 +0,0 @@
/*
Package color is an ANSI color package to output colorized or SGR defined
output to the standard output. The API can be used in several way, pick one
that suits you.
Use simple and default helper functions with predefined foreground colors:
color.Cyan("Prints text in cyan.")
// a newline will be appended automatically
color.Blue("Prints %s in blue.", "text")
// More default foreground colors..
color.Red("We have red")
color.Yellow("Yellow color too!")
color.Magenta("And many others ..")
// Hi-intensity colors
color.HiGreen("Bright green color.")
color.HiBlack("Bright black means gray..")
color.HiWhite("Shiny white color!")
However there are times where custom color mixes are required. Below are some
examples to create custom color objects and use the print functions of each
separate color object.
// Create a new color object
c := color.New(color.FgCyan).Add(color.Underline)
c.Println("Prints cyan text with an underline.")
// Or just add them to New()
d := color.New(color.FgCyan, color.Bold)
d.Printf("This prints bold cyan %s\n", "too!.")
// Mix up foreground and background colors, create new mixes!
red := color.New(color.FgRed)
boldRed := red.Add(color.Bold)
boldRed.Println("This will print text in bold red.")
whiteBackground := red.Add(color.BgWhite)
whiteBackground.Println("Red text with White background.")
// Use your own io.Writer output
color.New(color.FgBlue).Fprintln(myWriter, "blue color!")
blue := color.New(color.FgBlue)
blue.Fprint(myWriter, "This will print text in blue.")
You can create PrintXxx functions to simplify even more:
// Create a custom print function for convenient
red := color.New(color.FgRed).PrintfFunc()
red("warning")
red("error: %s", err)
// Mix up multiple attributes
notice := color.New(color.Bold, color.FgGreen).PrintlnFunc()
notice("don't forget this...")
You can also FprintXxx functions to pass your own io.Writer:
blue := color.New(FgBlue).FprintfFunc()
blue(myWriter, "important notice: %s", stars)
// Mix up with multiple attributes
success := color.New(color.Bold, color.FgGreen).FprintlnFunc()
success(myWriter, don't forget this...")
Or create SprintXxx functions to mix strings with other non-colorized strings:
yellow := New(FgYellow).SprintFunc()
red := New(FgRed).SprintFunc()
fmt.Printf("this is a %s and this is %s.\n", yellow("warning"), red("error"))
info := New(FgWhite, BgGreen).SprintFunc()
fmt.Printf("this %s rocks!\n", info("package"))
Windows support is enabled by default. All Print functions work as intended.
However only for color.SprintXXX functions, user should use fmt.FprintXXX and
set the output to color.Output:
fmt.Fprintf(color.Output, "Windows support: %s", color.GreenString("PASS"))
info := New(FgWhite, BgGreen).SprintFunc()
fmt.Fprintf(color.Output, "this %s rocks!\n", info("package"))
Using with existing code is possible. Just use the Set() method to set the
standard output to the given parameters. That way a rewrite of an existing
code is not required.
// Use handy standard colors.
color.Set(color.FgYellow)
fmt.Println("Existing text will be now in Yellow")
fmt.Printf("This one %s\n", "too")
color.Unset() // don't forget to unset
// You can mix up parameters
color.Set(color.FgMagenta, color.Bold)
defer color.Unset() // use it in your function
fmt.Println("All text will be now bold magenta.")
There might be a case where you want to disable color output (for example to
pipe the standard output of your app to somewhere else). `Color` has support to
disable colors both globally and for single color definition. For example
suppose you have a CLI app and a `--no-color` bool flag. You can easily disable
the color output with:
var flagNoColor = flag.Bool("no-color", false, "Disable color output")
if *flagNoColor {
color.NoColor = true // disables colorized output
}
It also has support for single color definitions (local). You can
disable/enable color output on the fly:
c := color.New(color.FgCyan)
c.Println("Prints cyan text")
c.DisableColor()
c.Println("This is printed without any color")
c.EnableColor()
c.Println("This prints again cyan...")
*/
package color

View File

@ -1,23 +0,0 @@
# 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

View File

@ -1,11 +0,0 @@
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

View File

@ -1,21 +0,0 @@
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.

View File

@ -1,163 +0,0 @@
# 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

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