<commit_before>package main

import (
	"fmt"
	"io"
	"os"
)

// $ glacier us-east-1 job inventory <vault> <topic> <description>
// $ glacier us-east-1 job archive <vault> <archive> <topic> <description>
// $ glacier us-east-1 job list <vault>
// $ glacier us-east-1 job describe <vault> <job>
// $ glacier us-east-1 job get inventory <vault> <job>
// $ glacier us-east-1 job get archive <vault> <job> <file>

func job(args []string) {
	if len(args) < 1 {
		fmt.Println("no job command")
		os.Exit(1)
	}
	command := args[0]
	args = args[1:]

	switch command {
	case "inventory":
		if len(args) < 1 {
			fmt.Println("no vault")
			os.Exit(1)
		}
		vault := args[0]
		args = args[1:]

		var description, topic string
		if len(args) > 0 {
			topic = args[0]
		}
		if len(args) > 1 {
			description = args[1]
		}

		jobId, err := connection.InitiateInventoryJob(vault, topic, description)
		if err != nil {
			fmt.Println(err)
			os.Exit(1)
		}
		fmt.Println(jobId)

	case "archive":
		if len(args) < 2 {
			fmt.Println("no vault")
			os.Exit(1)
		}
		vault := args[0]
		archive := args[1]
		args = args[2:]

		var description, topic string
		if len(args) > 0 {
			topic = args[0]
		}
		if len(args) > 1 {
			description = args[1]
		}

		jobId, err := connection.InitiateRetrievalJob(vault, archive, topic, description)
		if err != nil {
			fmt.Println(err)
			os.Exit(1)
		}
		fmt.Println(jobId)

	case "list":
		if len(args) < 1 {
			fmt.Println("no vault")
			os.Exit(1)
		}
		vault := args[0]

		jobs, _, err := connection.ListJobs(vault, "", "", "", 0)
		if err != nil {
			fmt.Println(err)
			os.Exit(1)
		}

		for _, v := range jobs {
			fmt.Println("Action:", v.Action)
			if v.Action == "ArchiveRetrieval" {
				fmt.Println("Archive ID:", v.ArchiveId)
				fmt.Println("Archive Size:", v.ArchiveSizeInBytes, prettySize(v.ArchiveSizeInBytes))
			}
			fmt.Println("Completed:", v.Completed)
			if v.Completed {
				fmt.Println("Completion Date:", v.CompletionDate)
			}
			fmt.Println("Creation Date:", v.CreationDate)
			if v.Action == "InventoryRetrieval" { // only know size when completed?
				fmt.Println("Invenotry Size:", v.InventorySizeInBytes, prettySize(uint64(v.InventorySizeInBytes)))
			}
			fmt.Println("Job Description:", v.JobDescription)
			fmt.Println("Job ID:", v.JobId)
			if v.Action == "ArchiveRetrieval" {
				fmt.Println("SHA256 Tree Hash:", v.SHA256TreeHash)
			}
			fmt.Println("SNS Topic:", v.SNSTopic)
			fmt.Println("Status Code:", v.StatusCode)
			fmt.Println("Status Message:", v.StatusMessage)
			fmt.Println("Vault ARN:", v.VaultARN)
			fmt.Println()
		}

	case "describe":
		if len(args) < 2 {
			fmt.Println("no vault and/or job id")
			os.Exit(1)
		}
		vault := args[0]
		jobId := args[1]

		job, err := connection.DescribeJob(vault, jobId)
		if err != nil {
			fmt.Println(err)
			os.Exit(1)
		}

		fmt.Println("Action:", job.Action)
		if job.Action == "ArchiveRetrieval" {
			fmt.Println("Archive ID:", job.ArchiveId)
			fmt.Println("Archive Size:", job.ArchiveSizeInBytes, prettySize(job.ArchiveSizeInBytes))
		}
		fmt.Println("Completed:", job.Completed)
		if job.Completed {
			fmt.Println("Completion Date:", job.CompletionDate)
		}
		fmt.Println("Creation Date:", job.CreationDate)
		if job.Action == "InventoryRetrieval" { // only know size when completed?
			fmt.Println("Invenotry Size:", job.InventorySizeInBytes, prettySize(uint64(job.InventorySizeInBytes)))
		}
		fmt.Println("Job Description:", job.JobDescription)
		fmt.Println("Job ID:", job.JobId)
		if job.Action == "ArchiveRetrieval" {
			fmt.Println("SHA256 Tree Hash:", job.SHA256TreeHash)
		}
		fmt.Println("SNS Topic:", job.SNSTopic)
		fmt.Println("Status Code:", job.StatusCode)
		fmt.Println("Status Message:", job.StatusMessage)
		fmt.Println("Vault ARN:", job.VaultARN)

	case "get":
		if len(args) < 1 {
			fmt.Println("no job sub command")
			os.Exit(1)
		}
		subCommand := args[0]
		args = args[1:]

		switch subCommand {
		case "inventory":
			if len(args) < 2 {
				fmt.Println("no vault and/or job id")
				os.Exit(1)
			}
			vault := args[0]
			job := args[1]

			inventory, err := connection.GetInventoryJob(vault, job)
			if err != nil {
				fmt.Println(err)
				os.Exit(1)
			}

			fmt.Println("Vault ARN:", inventory.VaultARN)
			fmt.Println("Inventory Date:", inventory.InventoryDate)
			for _, v := range inventory.ArchiveList {
				fmt.Println("Archive ID:", v.ArchiveId)
				fmt.Println("Archive Description:", v.ArchiveDescription)
				fmt.Println("Creation Date:", v.CreationDate)
				fmt.Println("Size:", v.Size)
				fmt.Println("SHA256 Tree Hash:", v.SHA256TreeHash)
				fmt.Println()
			}

		case "archive":
			// TODO retrieve parts and handle errors
			if len(args) < 3 {
				fmt.Println("no vault, job id, and/or output file")
				os.Exit(1)
			}
			vault := args[0]
			job := args[1]
			fileName := args[2]

			file, err := os.Create(fileName)
			if err != nil {
				fmt.Println(err)
				os.Exit(1)
			}
			defer file.Close()

			archive, err := connection.GetRetrievalJob(vault, job, 0, 0)
			if err != nil {
				fmt.Println(err)
				os.Exit(1)
			}
			defer archive.Close()

			_, err = io.Copy(file, archive)
			if err != nil {
				fmt.Println(err)
				os.Exit(1)
			}
		default:
			fmt.Println("unknown job sub command:", subCommand)
			os.Exit(1)
		}
	default:
		fmt.Println("unknown job command:", command)
		os.Exit(1)
	}
}
<commit_msg>Avoid printing useless information.<commit_after>package main

import (
	"fmt"
	"io"
	"os"
)

// $ glacier us-east-1 job inventory <vault> <topic> <description>
// $ glacier us-east-1 job archive <vault> <archive> <topic> <description>
// $ glacier us-east-1 job list <vault>
// $ glacier us-east-1 job describe <vault> <job>
// $ glacier us-east-1 job get inventory <vault> <job>
// $ glacier us-east-1 job get archive <vault> <job> <file>

func job(args []string) {
	if len(args) < 1 {
		fmt.Println("no job command")
		os.Exit(1)
	}
	command := args[0]
	args = args[1:]

	switch command {
	case "inventory":
		if len(args) < 1 {
			fmt.Println("no vault")
			os.Exit(1)
		}
		vault := args[0]
		args = args[1:]

		var description, topic string
		if len(args) > 0 {
			topic = args[0]
		}
		if len(args) > 1 {
			description = args[1]
		}

		jobId, err := connection.InitiateInventoryJob(vault, topic, description)
		if err != nil {
			fmt.Println(err)
			os.Exit(1)
		}
		fmt.Println(jobId)

	case "archive":
		if len(args) < 2 {
			fmt.Println("no vault")
			os.Exit(1)
		}
		vault := args[0]
		archive := args[1]
		args = args[2:]

		var description, topic string
		if len(args) > 0 {
			topic = args[0]
		}
		if len(args) > 1 {
			description = args[1]
		}

		jobId, err := connection.InitiateRetrievalJob(vault, archive, topic, description)
		if err != nil {
			fmt.Println(err)
			os.Exit(1)
		}
		fmt.Println(jobId)

	case "list":
		if len(args) < 1 {
			fmt.Println("no vault")
			os.Exit(1)
		}
		vault := args[0]

		jobs, _, err := connection.ListJobs(vault, "", "", "", 0)
		if err != nil {
			fmt.Println(err)
			os.Exit(1)
		}

		for _, v := range jobs {
			fmt.Println("Action:", v.Action)
			if v.Action == "ArchiveRetrieval" {
				fmt.Println("Archive ID:", v.ArchiveId)
				fmt.Println("Archive Size:", v.ArchiveSizeInBytes, prettySize(v.ArchiveSizeInBytes))
			}
			fmt.Println("Completed:", v.Completed)
			if v.Completed {
				fmt.Println("Completion Date:", v.CompletionDate)
			}
			fmt.Println("Creation Date:", v.CreationDate)
			if v.Completed && v.Action == "InventoryRetrieval" {
				fmt.Println("Invenotry Size:", v.InventorySizeInBytes, prettySize(uint64(v.InventorySizeInBytes)))
			}
			fmt.Println("Job Description:", v.JobDescription)
			fmt.Println("Job ID:", v.JobId)
			if v.Action == "ArchiveRetrieval" {
				fmt.Println("SHA256 Tree Hash:", v.SHA256TreeHash)
			}
			fmt.Println("SNS Topic:", v.SNSTopic)
			fmt.Println("Status Code:", v.StatusCode)
			fmt.Println("Status Message:", v.StatusMessage)
			fmt.Println("Vault ARN:", v.VaultARN)
			fmt.Println()
		}

	case "describe":
		if len(args) < 2 {
			fmt.Println("no vault and/or job id")
			os.Exit(1)
		}
		vault := args[0]
		jobId := args[1]

		job, err := connection.DescribeJob(vault, jobId)
		if err != nil {
			fmt.Println(err)
			os.Exit(1)
		}

		fmt.Println("Action:", job.Action)
		if job.Action == "ArchiveRetrieval" {
			fmt.Println("Archive ID:", job.ArchiveId)
			fmt.Println("Archive Size:", job.ArchiveSizeInBytes, prettySize(job.ArchiveSizeInBytes))
		}
		fmt.Println("Completed:", job.Completed)
		if job.Completed {
			fmt.Println("Completion Date:", job.CompletionDate)
		}
		fmt.Println("Creation Date:", job.CreationDate)
		if job.Completed && job.Action == "InventoryRetrieval" {
			fmt.Println("Invenotry Size:", job.InventorySizeInBytes, prettySize(uint64(job.InventorySizeInBytes)))
		}
		fmt.Println("Job Description:", job.JobDescription)
		fmt.Println("Job ID:", job.JobId)
		if job.Action == "ArchiveRetrieval" {
			fmt.Println("SHA256 Tree Hash:", job.SHA256TreeHash)
		}
		fmt.Println("SNS Topic:", job.SNSTopic)
		fmt.Println("Status Code:", job.StatusCode)
		fmt.Println("Status Message:", job.StatusMessage)
		fmt.Println("Vault ARN:", job.VaultARN)

	case "get":
		if len(args) < 1 {
			fmt.Println("no job sub command")
			os.Exit(1)
		}
		subCommand := args[0]
		args = args[1:]

		switch subCommand {
		case "inventory":
			if len(args) < 2 {
				fmt.Println("no vault and/or job id")
				os.Exit(1)
			}
			vault := args[0]
			job := args[1]

			inventory, err := connection.GetInventoryJob(vault, job)
			if err != nil {
				fmt.Println(err)
				os.Exit(1)
			}

			fmt.Println("Vault ARN:", inventory.VaultARN)
			fmt.Println("Inventory Date:", inventory.InventoryDate)
			for _, v := range inventory.ArchiveList {
				fmt.Println("Archive ID:", v.ArchiveId)
				fmt.Println("Archive Description:", v.ArchiveDescription)
				fmt.Println("Creation Date:", v.CreationDate)
				fmt.Println("Size:", v.Size)
				fmt.Println("SHA256 Tree Hash:", v.SHA256TreeHash)
				fmt.Println()
			}

		case "archive":
			// TODO retrieve parts and handle errors
			if len(args) < 3 {
				fmt.Println("no vault, job id, and/or output file")
				os.Exit(1)
			}
			vault := args[0]
			job := args[1]
			fileName := args[2]

			file, err := os.Create(fileName)
			if err != nil {
				fmt.Println(err)
				os.Exit(1)
			}
			defer file.Close()

			archive, err := connection.GetRetrievalJob(vault, job, 0, 0)
			if err != nil {
				fmt.Println(err)
				os.Exit(1)
			}
			defer archive.Close()

			_, err = io.Copy(file, archive)
			if err != nil {
				fmt.Println(err)
				os.Exit(1)
			}
		default:
			fmt.Println("unknown job sub command:", subCommand)
			os.Exit(1)
		}
	default:
		fmt.Println("unknown job command:", command)
		os.Exit(1)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"code.google.com/p/gosshold/ssh"
	"errors"
	"fmt"
	"io"
	"launchpad.net/goamz/aws"
	"launchpad.net/goamz/ec2"
	"log"
	"os"
	"os/exec"
	"path"
	"strings"
	"syscall"
)

var ErrNoInstancesFound = errors.New("No instances found; run provisioner first")

const AfterDeployHookScript = ".moltar-after-deploy"

type Job struct {
	region                  aws.Region
	env                     string
	cluster                 string
	project                 string
	packageNames            []string
	instances               []*ec2.Instance
	instanceSshClients      map[*ec2.Instance]*ssh.ClientConn
	instanceLoggers         map[*ec2.Instance]*log.Logger
	output                  io.Writer
	logger                  *log.Logger
	installVersionRev       uint64
	shouldOutputAnsiEscapes bool
}

func getInstancesTagged(ec2client *ec2.EC2, project string, env string, cluster string, packageName string) (instances []*ec2.Instance, err error) {
	instanceFilter := ec2.NewFilter()
	instanceFilter.Add("instance-state-name", "running")
	instanceFilter.Add("tag:Project", project)
	queryEnv := env
	if env == "" {
		queryEnv = "*"
	}
	instanceFilter.Add("tag:Environment", queryEnv)
	if cluster != "" {
		instanceFilter.Add("tag:Cluster", cluster)
	}

	if packageName != "" {
		instanceFilter.Add("tag:Packages", "*|"+packageName+"|*")
	}

	instancesResp, err := ec2client.Instances(nil, instanceFilter)
	if err != nil {
		return
	}

	instances = make([]*ec2.Instance, 0, 20)
	for _, res := range instancesResp.Reservations {
		for _, inst := range res.Instances {
			newInst := inst
			instances = append(instances, &newInst)
		}
	}

	return instances, nil
}

func NewJob(awsConf AWSConf, env string, cluster string, project string, packageNames []string, output io.Writer, shouldOutputAnsiEscapes bool) (job *Job, err error) {
	e := ec2.New(awsConf.Auth, awsConf.Region)

	var searchPackageNames []string
	if len(packageNames) == 0 {
		searchPackageNames = []string{""}
	} else {
		searchPackageNames = packageNames[:]
	}

	instancesSet := map[string]*ec2.Instance{}
	for _, packageName := range searchPackageNames {
		instances, err := getInstancesTagged(e, project, env, cluster, packageName)
		if err != nil {
			return nil, err
		}

		for _, instance := range instances {
			instancesSet[instance.InstanceId] = instance
		}
	}

	instances := make([]*ec2.Instance, 0, len(instancesSet))
	for _, instance := range instancesSet {
		instances = append(instances, instance)
	}

	if len(instances) == 0 {
		return nil, ErrNoInstancesFound
	}

	logger := log.New(output, "", 0)

	return &Job{region: awsConf.Region, env: env, cluster: cluster,
		project: project, packageNames: packageNames, instances: instances,
		instanceSshClients: make(map[*ec2.Instance]*ssh.ClientConn),
		instanceLoggers:    make(map[*ec2.Instance]*log.Logger),
		output:             output, logger: logger,
		shouldOutputAnsiEscapes: shouldOutputAnsiEscapes}, nil
}

func (self *Job) Exec(cmd string) (errs []error) {
	errChan := make(chan error, len(self.instances))
	errs = make([]error, 0, len(self.instances))

	for _, instance := range self.instances {
		go func(inst ec2.Instance) {
			conn, err := self.sshClient(&inst)
			if err != nil {
				errChan <- err
				return
			}

			logger := self.instanceLogger(&inst)
			_, returnChan, err := sshRunOutLogger(conn, cmd, logger, nil)
			if err == nil {
				err = <-returnChan
			} else {
				errChan <- err
			}
			errChan <- err
		}(*instance)
	}
	startStdinRead()

	for _ = range self.instances {
		if err := <-errChan; err != nil {
			errs = append(errs, err)
		}
	}
	return
}

func (self *Job) ExecList(cmds []string) (errs []error) {
	for _, cmd := range cmds {
		fmt.Printf("\n%s\n\n", cmd)
		errs = self.Exec(cmd)
		if len(errs) > 0 {
			return
		}
	}
	return []error{}
}

func (self *Job) Deploy() (errs []error) {
	errs = self.ExecList([]string{
		"sudo apt-get update -qq",
		"sudo DEBIAN_FRONTEND=noninteractive apt-get install -qy '" +
			strings.Join(self.packageNames, "' '") + "'",
		"sudo DEBIAN_FRONTEND=noninteractive apt-get autoremove -yq",
		"sudo apt-get clean -yq",
	})
	if len(errs) > 0 {
		return
	}

	if _, err := os.Stat(AfterDeployHookScript); err != nil {
		return
	}

	prepareExec()
	pwd, err := os.Getwd()
	if err != nil {
		return
	}
	syscall.Exec(path.Join(pwd, AfterDeployHookScript),
		[]string{AfterDeployHookScript},
		append(os.Environ(), "ENV="+self.env))

	return
}

func (self *Job) Ssh(criteria string, sshArgs []string) (err error) {
	sshPath, err := exec.LookPath("ssh")
	if err != nil {
		return
	}

	var instance *ec2.Instance
	matches := self.instances

	if criteria != "-1" {
		if criteria != "" {
			matches = make([]*ec2.Instance, 0, len(self.instances))
			for _, instance = range self.instances {
				if matchCriteria(instance, criteria) {
					instanceLogName(instance)
					matches = append(matches, instance)
				}
			}
		}

		if len(matches) == 0 {
			self.logger.Fatalf("Instance '%s' not found\n", criteria)
		} else if len(matches) > 1 {
			self.logger.Printf("Multiple matches for '%s' found:\n", criteria)
			self.printInstances(matches)
			self.logger.Fatal("")
		}
	}

	instance = matches[0]

	execArgs := []string{"ssh"}
	keyFile := self.keyFile()
	if keyFile != "" {
		execArgs = append(execArgs, "-i", keyFile)
	}

	execArgs = append(execArgs,
		fmt.Sprintf("%s@%s", self.sshUserName(instance), instance.DNSName))
	execArgs = append(execArgs, sshArgs...)

	fPrintShellCommand(self.output, "", execArgs)
	fmt.Fprintln(self.output, "")

	prepareExec()
	err = syscall.Exec(sshPath, execArgs, os.Environ())
	return
}

func (self *Job) Scp(args []string) (err error) {
	scpPath, err := exec.LookPath("scp")
	if err != nil {
		return
	}

	defaultArgs := []string{"-q"}
	keyFile := self.keyFile()
	if keyFile != "" {
		defaultArgs = append(defaultArgs, []string{
			"-i", keyFile,
		}...)
	}
	scpArgs := make([]string, len(defaultArgs)+len(args))
	copy(scpArgs, defaultArgs)
	copy(scpArgs[len(defaultArgs):], args)

	var dstIndex = -1
	for i, arg := range scpArgs {
		if arg[0] == ':' {
			dstIndex = i
			break
		}
	}
	if dstIndex == -1 {
		dstIndex = len(scpArgs)
		scpArgs = append(scpArgs, ":")
	}

	errChan := make(chan error, len(self.instances))

	for _, instance := range self.instances {
		go func(instance *ec2.Instance) {
			var err error
			args := make([]string, len(scpArgs))
			copy(args, scpArgs)

			logger := self.instanceLogger(instance)
			args[dstIndex] = fmt.Sprintf("%s@%s%s",
				self.sshUserName(instance), instance.DNSName, args[dstIndex])

			fPrintShellCommand(self.output, "scp", args)

			cmd := exec.Command(scpPath, args...)
			outPipeRead, outPipeWrite, err := os.Pipe()
			if err != nil {
				logger.Printf("error creating pipe: %s\n", err)
				errChan <- err
				return
			}
			cmd.Stdout = outPipeWrite
			cmd.Stderr = outPipeWrite

			err = cmd.Start()
			if err != nil {
				logger.Printf("error starting scp: %s\n", err)
				errChan <- err
				return
			}

			outPipeWrite.Close()
			stdoutReader := bufio.NewReader(outPipeRead)
			for {
				in, err := stdoutReader.ReadString('\n')
				if (err == io.EOF && in != "") || err == nil {
					logger.Print(in)
				}
				if err != nil {
					break
				}
			}

			err = cmd.Wait()
			outPipeRead.Close()
			if err != nil {
				logger.Printf("error running scp: %s\n", err)
			}
			errChan <- err
		}(instance)
	}

	var scpErr error
	for _ = range self.instances {
		scpErr = <-errChan
		if err == nil && scpErr != nil {
			err = errors.New("at least one scp failed")
		}
	}
	return
}

func (self *Job) List() (err error) {
	self.printInstances(self.instances)
	return nil
}

func (self *Job) Hostname(instanceName string) (err error) {
	for _, instance := range self.instances {
		if instanceLogName(instance) == instanceName {
			fmt.Fprintln(self.output, instance.DNSName)
			return nil
		}
	}
	return errors.New(instanceName + " not found")
}

/// Subtasks

func (self *Job) sshClient(i *ec2.Instance) (conn *ssh.ClientConn, err error) {
	conn = self.instanceSshClients[i]
	if conn == nil {
		conn, err = self.sshDial(i)
		if err == nil {
			self.instanceSshClients[i] = conn
		}
	}
	return
}

func (self *Job) instanceLogger(i *ec2.Instance) (logger *log.Logger) {
	logger = self.instanceLoggers[i]
	if logger == nil {
		prefix := instanceLogName(i)
		if self.shouldOutputAnsiEscapes {
			prefix = "\033[1m" + prefix + "\033[0m"
		}
		logger = log.New(self.output, prefix+" ", 0)
		self.instanceLoggers[i] = logger
	}
	return
}

func (self *Job) keyFile() (path string) {
	fileName := self.project
	if len(self.packageNames) > 0 {
		fileName += fmt.Sprintf("-%s", self.packageNames[0])
	}
	path = fmt.Sprintf(os.ExpandEnv("${HOME}/Google Drive/%s Ops/Keys/%s.pem"),
		self.project, fileName)

	if _, err := os.Stat(path); err == nil {
		return path
	} else {
		return ""
	}
}

func (self *Job) sshUserName(_ *ec2.Instance) (userName string) {
	// TODO: be more clever about this
	return "ubuntu"
}

func (self *Job) sshDial(i *ec2.Instance) (conn *ssh.ClientConn, err error) {
	conn, err = sshDial(i.DNSName+":22", self.sshUserName(i), self.keyFile())
	return
}

func (self *Job) printInstances(instances []*ec2.Instance) {
	fields := make([][]string, len(instances))
	for i, instance := range instances {
		fields[i] = []string{instance.InstanceId, instanceLogName(instance),
			instance.DNSName}
	}
	fmt.Fprint(self.output, formatTable(fields))
}

func instanceLogName(i *ec2.Instance) string {
	for _, tag := range i.Tags {
		if tag.Key == "Name" && tag.Value != "" {
			return tag.Value
		}
	}
	return i.InstanceId
}

func fPrintShellCommand(w io.Writer, n string, cmd []string) {
	if n != "" {
		fmt.Fprintf(w, "%s ", n)
	}
	for i, cmdPart := range cmd {
		// TODO: this escaping will work most of the time, but isn't that great
		if strings.ContainsAny(cmdPart, " $") {
			fmt.Fprintf(w, "'%s'", cmdPart)
		} else {
			fmt.Fprint(w, cmdPart)
		}
		if i < (len(cmd) - 1) {
			fmt.Fprint(w, " ")
		}
	}
	fmt.Fprint(w, "\n")
}

func matchCriteria(instance *ec2.Instance, criteria string) bool {
	var found bool
	for _, value := range strings.Split(criteria, "/") {
		found = false
		for _, tag := range instance.Tags {
			if strings.Contains(tag.Value, value) {
				found = true
				break
			}
		}
		if !strings.Contains(instance.InstanceId, value) && !strings.Contains(instance.PrivateDNSName, value) && !strings.Contains(instance.DNSName, value) && found == false {
			return false
		}
	}
	return true
}

func prepareExec() {
	/* There appears to be a bug with goamz where some fds are left open, and
	 * just closing them causes a crash. If we ask all fds > 2 to close on
	 * exec, all is well.
	 */
	var rlimit syscall.Rlimit
	err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rlimit)
	if err != nil {
		panic(err)
	}
	maxFds := int(rlimit.Cur)
	for fd := 3; fd < maxFds; fd++ {
		syscall.CloseOnExec(fd)
	}
}
<commit_msg>Package matching is now "all instances" rather than "any instances"<commit_after>package main

import (
	"bufio"
	"code.google.com/p/gosshold/ssh"
	"errors"
	"fmt"
	"io"
	"launchpad.net/goamz/aws"
	"launchpad.net/goamz/ec2"
	"log"
	"os"
	"os/exec"
	"path"
	"strings"
	"syscall"
)

var ErrNoInstancesFound = errors.New("No instances found; run provisioner first")

const AfterDeployHookScript = ".moltar-after-deploy"

type Job struct {
	region                  aws.Region
	env                     string
	cluster                 string
	project                 string
	packageNames            []string
	instances               []*ec2.Instance
	instanceSshClients      map[*ec2.Instance]*ssh.ClientConn
	instanceLoggers         map[*ec2.Instance]*log.Logger
	output                  io.Writer
	logger                  *log.Logger
	installVersionRev       uint64
	shouldOutputAnsiEscapes bool
}

func getInstancesTagged(ec2client *ec2.EC2, project string, env string, cluster string, packageName string) (instances []*ec2.Instance, err error) {
	instanceFilter := ec2.NewFilter()
	instanceFilter.Add("instance-state-name", "running")
	instanceFilter.Add("tag:Project", project)
	queryEnv := env
	if env == "" {
		queryEnv = "*"
	}
	instanceFilter.Add("tag:Environment", queryEnv)
	if cluster != "" {
		instanceFilter.Add("tag:Cluster", cluster)
	}

	if packageName != "" {
		instanceFilter.Add("tag:Packages", "*|"+packageName+"|*")
	}

	instancesResp, err := ec2client.Instances(nil, instanceFilter)
	if err != nil {
		return
	}

	instances = make([]*ec2.Instance, 0, 20)
	for _, res := range instancesResp.Reservations {
		for _, inst := range res.Instances {
			newInst := inst
			instances = append(instances, &newInst)
		}
	}

	return instances, nil
}

func NewJob(awsConf AWSConf, env string, cluster string, project string, packageNames []string, output io.Writer, shouldOutputAnsiEscapes bool) (job *Job, err error) {
	e := ec2.New(awsConf.Auth, awsConf.Region)

	var searchPackageNames []string
	if len(packageNames) == 0 {
		searchPackageNames = []string{""}
	} else {
		searchPackageNames = packageNames[:]
	}

	instancesSet := map[string]*ec2.Instance{}
	instancesCount := map[string]int{}
	for _, packageName := range searchPackageNames {
		instances, err := getInstancesTagged(e, project, env, cluster, packageName)
		if err != nil {
			return nil, err
		}

		for _, instance := range instances {
			instancesSet[instance.InstanceId] = instance
			instancesCount[instance.InstanceId] += 1
		}
	}

	instances := make([]*ec2.Instance, 0, len(instancesSet))
	for _, instance := range instancesSet {
		if instancesCount[instance.InstanceId] == len(searchPackageNames) {
			instances = append(instances, instance)
		}
	}

	if len(instances) == 0 {
		return nil, ErrNoInstancesFound
	}

	logger := log.New(output, "", 0)

	return &Job{region: awsConf.Region, env: env, cluster: cluster,
		project: project, packageNames: packageNames, instances: instances,
		instanceSshClients: make(map[*ec2.Instance]*ssh.ClientConn),
		instanceLoggers:    make(map[*ec2.Instance]*log.Logger),
		output:             output, logger: logger,
		shouldOutputAnsiEscapes: shouldOutputAnsiEscapes}, nil
}

func (self *Job) Exec(cmd string) (errs []error) {
	errChan := make(chan error, len(self.instances))
	errs = make([]error, 0, len(self.instances))

	for _, instance := range self.instances {
		go func(inst ec2.Instance) {
			conn, err := self.sshClient(&inst)
			if err != nil {
				errChan <- err
				return
			}

			logger := self.instanceLogger(&inst)
			_, returnChan, err := sshRunOutLogger(conn, cmd, logger, nil)
			if err == nil {
				err = <-returnChan
			} else {
				errChan <- err
			}
			errChan <- err
		}(*instance)
	}
	startStdinRead()

	for _ = range self.instances {
		if err := <-errChan; err != nil {
			errs = append(errs, err)
		}
	}
	return
}

func (self *Job) ExecList(cmds []string) (errs []error) {
	for _, cmd := range cmds {
		fmt.Printf("\n%s\n\n", cmd)
		errs = self.Exec(cmd)
		if len(errs) > 0 {
			return
		}
	}
	return []error{}
}

func (self *Job) Deploy() (errs []error) {
	errs = self.ExecList([]string{
		"sudo apt-get update -qq",
		"sudo DEBIAN_FRONTEND=noninteractive apt-get install -qy '" +
			strings.Join(self.packageNames, "' '") + "'",
		"sudo DEBIAN_FRONTEND=noninteractive apt-get autoremove -yq",
		"sudo apt-get clean -yq",
	})
	if len(errs) > 0 {
		return
	}

	if _, err := os.Stat(AfterDeployHookScript); err != nil {
		return
	}

	prepareExec()
	pwd, err := os.Getwd()
	if err != nil {
		return
	}
	syscall.Exec(path.Join(pwd, AfterDeployHookScript),
		[]string{AfterDeployHookScript},
		append(os.Environ(), "ENV="+self.env))

	return
}

func (self *Job) Ssh(criteria string, sshArgs []string) (err error) {
	sshPath, err := exec.LookPath("ssh")
	if err != nil {
		return
	}

	var instance *ec2.Instance
	matches := self.instances

	if criteria != "-1" {
		if criteria != "" {
			matches = make([]*ec2.Instance, 0, len(self.instances))
			for _, instance = range self.instances {
				if matchCriteria(instance, criteria) {
					instanceLogName(instance)
					matches = append(matches, instance)
				}
			}
		}

		if len(matches) == 0 {
			self.logger.Fatalf("Instance '%s' not found\n", criteria)
		} else if len(matches) > 1 {
			self.logger.Printf("Multiple matches for '%s' found:\n", criteria)
			self.printInstances(matches)
			self.logger.Fatal("")
		}
	}

	instance = matches[0]

	execArgs := []string{"ssh"}
	keyFile := self.keyFile()
	if keyFile != "" {
		execArgs = append(execArgs, "-i", keyFile)
	}

	execArgs = append(execArgs,
		fmt.Sprintf("%s@%s", self.sshUserName(instance), instance.DNSName))
	execArgs = append(execArgs, sshArgs...)

	fPrintShellCommand(self.output, "", execArgs)
	fmt.Fprintln(self.output, "")

	prepareExec()
	err = syscall.Exec(sshPath, execArgs, os.Environ())
	return
}

func (self *Job) Scp(args []string) (err error) {
	scpPath, err := exec.LookPath("scp")
	if err != nil {
		return
	}

	defaultArgs := []string{"-q"}
	keyFile := self.keyFile()
	if keyFile != "" {
		defaultArgs = append(defaultArgs, []string{
			"-i", keyFile,
		}...)
	}
	scpArgs := make([]string, len(defaultArgs)+len(args))
	copy(scpArgs, defaultArgs)
	copy(scpArgs[len(defaultArgs):], args)

	var dstIndex = -1
	for i, arg := range scpArgs {
		if arg[0] == ':' {
			dstIndex = i
			break
		}
	}
	if dstIndex == -1 {
		dstIndex = len(scpArgs)
		scpArgs = append(scpArgs, ":")
	}

	errChan := make(chan error, len(self.instances))

	for _, instance := range self.instances {
		go func(instance *ec2.Instance) {
			var err error
			args := make([]string, len(scpArgs))
			copy(args, scpArgs)

			logger := self.instanceLogger(instance)
			args[dstIndex] = fmt.Sprintf("%s@%s%s",
				self.sshUserName(instance), instance.DNSName, args[dstIndex])

			fPrintShellCommand(self.output, "scp", args)

			cmd := exec.Command(scpPath, args...)
			outPipeRead, outPipeWrite, err := os.Pipe()
			if err != nil {
				logger.Printf("error creating pipe: %s\n", err)
				errChan <- err
				return
			}
			cmd.Stdout = outPipeWrite
			cmd.Stderr = outPipeWrite

			err = cmd.Start()
			if err != nil {
				logger.Printf("error starting scp: %s\n", err)
				errChan <- err
				return
			}

			outPipeWrite.Close()
			stdoutReader := bufio.NewReader(outPipeRead)
			for {
				in, err := stdoutReader.ReadString('\n')
				if (err == io.EOF && in != "") || err == nil {
					logger.Print(in)
				}
				if err != nil {
					break
				}
			}

			err = cmd.Wait()
			outPipeRead.Close()
			if err != nil {
				logger.Printf("error running scp: %s\n", err)
			}
			errChan <- err
		}(instance)
	}

	var scpErr error
	for _ = range self.instances {
		scpErr = <-errChan
		if err == nil && scpErr != nil {
			err = errors.New("at least one scp failed")
		}
	}
	return
}

func (self *Job) List() (err error) {
	self.printInstances(self.instances)
	return nil
}

func (self *Job) Hostname(instanceName string) (err error) {
	for _, instance := range self.instances {
		if instanceLogName(instance) == instanceName {
			fmt.Fprintln(self.output, instance.DNSName)
			return nil
		}
	}
	return errors.New(instanceName + " not found")
}

/// Subtasks

func (self *Job) sshClient(i *ec2.Instance) (conn *ssh.ClientConn, err error) {
	conn = self.instanceSshClients[i]
	if conn == nil {
		conn, err = self.sshDial(i)
		if err == nil {
			self.instanceSshClients[i] = conn
		}
	}
	return
}

func (self *Job) instanceLogger(i *ec2.Instance) (logger *log.Logger) {
	logger = self.instanceLoggers[i]
	if logger == nil {
		prefix := instanceLogName(i)
		if self.shouldOutputAnsiEscapes {
			prefix = "\033[1m" + prefix + "\033[0m"
		}
		logger = log.New(self.output, prefix+" ", 0)
		self.instanceLoggers[i] = logger
	}
	return
}

func (self *Job) keyFile() (path string) {
	fileName := self.project
	if len(self.packageNames) > 0 {
		fileName += fmt.Sprintf("-%s", self.packageNames[0])
	}
	path = fmt.Sprintf(os.ExpandEnv("${HOME}/Google Drive/%s Ops/Keys/%s.pem"),
		self.project, fileName)

	if _, err := os.Stat(path); err == nil {
		return path
	} else {
		return ""
	}
}

func (self *Job) sshUserName(_ *ec2.Instance) (userName string) {
	// TODO: be more clever about this
	return "ubuntu"
}

func (self *Job) sshDial(i *ec2.Instance) (conn *ssh.ClientConn, err error) {
	conn, err = sshDial(i.DNSName+":22", self.sshUserName(i), self.keyFile())
	return
}

func (self *Job) printInstances(instances []*ec2.Instance) {
	fields := make([][]string, len(instances))
	for i, instance := range instances {
		fields[i] = []string{instance.InstanceId, instanceLogName(instance),
			instance.DNSName}
	}
	fmt.Fprint(self.output, formatTable(fields))
}

func instanceLogName(i *ec2.Instance) string {
	for _, tag := range i.Tags {
		if tag.Key == "Name" && tag.Value != "" {
			return tag.Value
		}
	}
	return i.InstanceId
}

func fPrintShellCommand(w io.Writer, n string, cmd []string) {
	if n != "" {
		fmt.Fprintf(w, "%s ", n)
	}
	for i, cmdPart := range cmd {
		// TODO: this escaping will work most of the time, but isn't that great
		if strings.ContainsAny(cmdPart, " $") {
			fmt.Fprintf(w, "'%s'", cmdPart)
		} else {
			fmt.Fprint(w, cmdPart)
		}
		if i < (len(cmd) - 1) {
			fmt.Fprint(w, " ")
		}
	}
	fmt.Fprint(w, "\n")
}

func matchCriteria(instance *ec2.Instance, criteria string) bool {
	var found bool
	for _, value := range strings.Split(criteria, "/") {
		found = false
		for _, tag := range instance.Tags {
			if strings.Contains(tag.Value, value) {
				found = true
				break
			}
		}
		if !strings.Contains(instance.InstanceId, value) && !strings.Contains(instance.PrivateDNSName, value) && !strings.Contains(instance.DNSName, value) && found == false {
			return false
		}
	}
	return true
}

func prepareExec() {
	/* There appears to be a bug with goamz where some fds are left open, and
	 * just closing them causes a crash. If we ask all fds > 2 to close on
	 * exec, all is well.
	 */
	var rlimit syscall.Rlimit
	err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rlimit)
	if err != nil {
		panic(err)
	}
	maxFds := int(rlimit.Cur)
	for fd := 3; fd < maxFds; fd++ {
		syscall.CloseOnExec(fd)
	}
}
<|endoftext|>


<commit_before>// Copyright 2013-2014 Aerospike, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package aerospike

import (
	"bytes"
	"fmt"
	"hash"

	"github.com/aerospike/aerospike-client-go/pkg/ripemd160"
	. "github.com/aerospike/aerospike-client-go/types"
	ParticleType "github.com/aerospike/aerospike-client-go/types/particle_type"
	Buffer "github.com/aerospike/aerospike-client-go/utils/buffer"
)

// Key is the unique record identifier. Records can be identified using a specified namespace,
// an optional set name, and a user defined key which must be unique within a set.
// Records can also be identified by namespace/digest which is the combination used
// on the server.
type Key struct {
	// namespace. Equivalent to database name.
	namespace string

	// Optional set name. Equivalent to database table.
	setName string

	// Unique server hash value generated from set name and user key.
	digest []byte

	// Original user key. This key is immediately converted to a hash digest.
	// This key is not used or returned by the server by default. If the user key needs
	// to persist on the server, use one of the following methods:
	//
	// Set "WritePolicy.sendKey" to true. In this case, the key will be sent to the server for storage on writes
	// and retrieved on multi-record scans and queries.
	// Explicitly store and retrieve the key in a bin.
	userKey Value
}

// Namespace returns key's namespace.
func (ky *Key) Namespace() string {
	return ky.namespace
}

// SetName returns key's set name.
func (ky *Key) SetName() string {
	return ky.setName
}

// Value returns key's value.
func (ky *Key) Value() Value {
	return ky.userKey
}

// Digest returns key digest.
func (ky *Key) Digest() []byte {
	return ky.digest
}

// Equals uses key digests to compare key equality.
func (ky *Key) Equals(other *Key) bool {
	return bytes.Equal(ky.digest, other.digest)
}

// String implements Stringer interface and returns string representation of key.
func (ky *Key) String() string {
	if ky.userKey != nil {
		return fmt.Sprintf("%s:%s:%s:%v", ky.namespace, ky.setName, ky.userKey.String(), Buffer.BytesToHexString(ky.digest))
	}
	return fmt.Sprintf("%s:%s::%v", ky.namespace, ky.setName, Buffer.BytesToHexString(ky.digest))
}

// NewKey initializes a key from namespace, optional set name and user key.
// The set name and user defined key are converted to a digest before sending to the server.
// The server handles record identifiers by digest only.
func NewKey(namespace string, setName string, key interface{}) (newKey *Key, err error) {
	newKey = &Key{
		namespace: namespace,
		setName:   setName,
		userKey:   NewValue(key),
	}

	newKey.digest, err = computeDigest(&newKey.setName, NewValue(key))

	return newKey, err
}

// Generate unique server hash value from set name, key type and user defined key.
// The hash function is RIPEMD-160 (a 160 bit hash).
func computeDigest(setName *string, key Value) ([]byte, error) {
	keyType := key.GetType()

	if keyType == ParticleType.NULL {
		return nil, NewAerospikeError(PARAMETER_ERROR, "Invalid key: nil")
	}

	// retrieve hash from hash pool
	h := hashPool.Get().(hash.Hash)
	h.Reset()

	// write will not fail; no error checking necessary
	h.Write([]byte(*setName))
	h.Write([]byte{byte(keyType)})
	h.Write(key.getBytes())

	// put hash object back to the pool
	hashPool.Put(h)

	return h.Sum(nil), nil
}

// hash pool
var hashPool *Pool

func init() {
	hashPool = NewPool(1024)
	hashPool.New = func() interface{} {
		return ripemd160.New()
	}
}
<commit_msg>fixed minor bug<commit_after>// Copyright 2013-2014 Aerospike, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package aerospike

import (
	"bytes"
	"fmt"
	"hash"

	"github.com/aerospike/aerospike-client-go/pkg/ripemd160"
	. "github.com/aerospike/aerospike-client-go/types"
	ParticleType "github.com/aerospike/aerospike-client-go/types/particle_type"
	Buffer "github.com/aerospike/aerospike-client-go/utils/buffer"
)

// Key is the unique record identifier. Records can be identified using a specified namespace,
// an optional set name, and a user defined key which must be unique within a set.
// Records can also be identified by namespace/digest which is the combination used
// on the server.
type Key struct {
	// namespace. Equivalent to database name.
	namespace string

	// Optional set name. Equivalent to database table.
	setName string

	// Unique server hash value generated from set name and user key.
	digest []byte

	// Original user key. This key is immediately converted to a hash digest.
	// This key is not used or returned by the server by default. If the user key needs
	// to persist on the server, use one of the following methods:
	//
	// Set "WritePolicy.sendKey" to true. In this case, the key will be sent to the server for storage on writes
	// and retrieved on multi-record scans and queries.
	// Explicitly store and retrieve the key in a bin.
	userKey Value
}

// Namespace returns key's namespace.
func (ky *Key) Namespace() string {
	return ky.namespace
}

// SetName returns key's set name.
func (ky *Key) SetName() string {
	return ky.setName
}

// Value returns key's value.
func (ky *Key) Value() Value {
	return ky.userKey
}

// Digest returns key digest.
func (ky *Key) Digest() []byte {
	return ky.digest
}

// Equals uses key digests to compare key equality.
func (ky *Key) Equals(other *Key) bool {
	return bytes.Equal(ky.digest, other.digest)
}

// String implements Stringer interface and returns string representation of key.
func (ky *Key) String() string {
	if ky.userKey != nil {
		return fmt.Sprintf("%s:%s:%s:%v", ky.namespace, ky.setName, ky.userKey.String(), Buffer.BytesToHexString(ky.digest))
	}
	return fmt.Sprintf("%s:%s::%v", ky.namespace, ky.setName, Buffer.BytesToHexString(ky.digest))
}

// NewKey initializes a key from namespace, optional set name and user key.
// The set name and user defined key are converted to a digest before sending to the server.
// The server handles record identifiers by digest only.
func NewKey(namespace string, setName string, key interface{}) (newKey *Key, err error) {
	newKey = &Key{
		namespace: namespace,
		setName:   setName,
		userKey:   NewValue(key),
	}

	newKey.digest, err = computeDigest(&newKey.setName, NewValue(key))

	return newKey, err
}

// Generate unique server hash value from set name, key type and user defined key.
// The hash function is RIPEMD-160 (a 160 bit hash).
func computeDigest(setName *string, key Value) ([]byte, error) {
	keyType := key.GetType()

	if keyType == ParticleType.NULL {
		return nil, NewAerospikeError(PARAMETER_ERROR, "Invalid key: nil")
	}

	// retrieve hash from hash pool
	h := hashPool.Get().(hash.Hash)
	h.Reset()

	// write will not fail; no error checking necessary
	h.Write([]byte(*setName))
	h.Write([]byte{byte(keyType)})
	h.Write(key.getBytes())

	res := h.Sum(nil)

	// put hash object back to the pool
	hashPool.Put(h)

	return res, nil
}

// hash pool
var hashPool *Pool

func init() {
	hashPool = NewPool(4096)
	hashPool.New = func() interface{} {
		return ripemd160.New()
	}
}
<|endoftext|>


<commit_before>package ari

const (
	// ApplicationKey is the key kind for ARI Application resources.
	ApplicationKey = "application"

	// BridgeKey is the key kind for the ARI Bridge resources.
	BridgeKey = "bridge"

	// ChannelKey is the key kind for the ARI Channel resource
	ChannelKey = "channel"
)

// Key identifies a unique resource in the system
type Key struct {
	// Kind indicates the type of resource the Key points to.  e.g., "channel",
	// "bridge", etc.
	Kind string `json:"kind"`

	// ID indicates the unique identifier of the resource
	ID string `json:"id"`

	// Node indicates the unique identifier of the Asterisk node on which the
	// resource exists or will be created
	Node string `json:"node,omitempty"`

	// Dialog indicates a named scope of the resource, for receiving events
	Dialog string `json:"dialog,omitempty"`
}

// KeyOptionFunc is a functional argument alias for providing options for ARI keys
type KeyOptionFunc func(Key)

// WithDialog sets the given dialog identifier on the key.
func WithDialog(dialog string) KeyOptionFunc {
	return func(key Key) {
		key.Dialog = dialog
	}
}

// WithNode sets the given node identifier on the key.
func WithNode(node string) KeyOptionFunc {
	return func(key Key) {
		key.Node = node
	}
}

// NewKey builds a new key given the kind, identifier, and any optional arguments.
func NewKey(kind string, id string, opts ...KeyOptionFunc) *Key {
	k := Key{
		Kind: kind,
		ID:   id,
	}
	for _, o := range opts {
		o(k)
	}

	return &k
}
<commit_msg>ari.key - WithApp and Key helpers<commit_after>package ari

const (
	// ApplicationKey is the key kind for ARI Application resources.
	ApplicationKey = "application"

	// BridgeKey is the key kind for the ARI Bridge resources.
	BridgeKey = "bridge"

	// ChannelKey is the key kind for the ARI Channel resource
	ChannelKey = "channel"
)

// Key identifies a unique resource in the system
type Key struct {
	// Kind indicates the type of resource the Key points to.  e.g., "channel",
	// "bridge", etc.
	Kind string `json:"kind"`

	// ID indicates the unique identifier of the resource
	ID string `json:"id"`

	// Node indicates the unique identifier of the Asterisk node on which the
	// resource exists or will be created
	Node string `json:"node,omitempty"`

	// Dialog indicates a named scope of the resource, for receiving events
	Dialog string `json:"dialog,omitempty"`

	// App indiciates the ARI application that this key is bound to.
	App string `json:"app,omitempty"`
}

// KeyOptionFunc is a functional argument alias for providing options for ARI keys
type KeyOptionFunc func(Key)

// WithDialog sets the given dialog identifier on the key.
func WithDialog(dialog string) KeyOptionFunc {
	return func(key Key) {
		key.Dialog = dialog
	}
}

// WithNode sets the given node identifier on the key.
func WithNode(node string) KeyOptionFunc {
	return func(key Key) {
		key.Node = node
	}
}

// WithApp sets the given node identifier on the key.
func WithApp(app string) KeyOptionFunc {
	return func(key Key) {
		key.App = app
	}
}

// WithParent copies the partial key fields Node, Application, Dialog from the parent key
func WithParent(parent *Key) KeyOptionFunc {
	return func(key Key) {
		key.Node = parent.Node
		key.Dialog = parent.Dialog
		key.App = parent.App
	}
}

// NewKey builds a new key given the kind, identifier, and any optional arguments.
func NewKey(kind string, id string, opts ...KeyOptionFunc) *Key {
	k := Key{
		Kind: kind,
		ID:   id,
	}
	for _, o := range opts {
		o(k)
	}

	return &k
}

// AppKey returns a key that is bound to the given application.
func AppKey(app string) *Key {
	return NewKey("", "", WithApp(app))
}

// DialogKey returns a key that is bound to the given dialog.
func DialogKey(dialog string) *Key {
	return NewKey("", "", WithDialog(dialog))
}

// NodeKey returns a key that is bound to the given application and node
func NodeKey(app, node string) *Key {
	return NewKey("", "", WithApp(app), WithNode(node))
}
<|endoftext|>


<commit_before>// Copyright 2016 go-dockerclient authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package docker

import (
	"context"
	"encoding/json"
	"io"
	"net/http"
	"time"

	"github.com/docker/docker/api/types/swarm"
)

// NoSuchService is the error returned when a given service does not exist.
type NoSuchService struct {
	ID  string
	Err error
}

func (err *NoSuchService) Error() string {
	if err.Err != nil {
		return err.Err.Error()
	}
	return "No such service: " + err.ID
}

// CreateServiceOptions specify parameters to the CreateService function.
//
// See https://goo.gl/KrVjHz for more details.
type CreateServiceOptions struct {
	Auth AuthConfiguration `qs:"-"`
	swarm.ServiceSpec
	Context context.Context
}

// CreateService creates a new service, returning the service instance
// or an error in case of failure.
//
// See https://goo.gl/KrVjHz for more details.
func (c *Client) CreateService(opts CreateServiceOptions) (*swarm.Service, error) {
	headers, err := headersWithAuth(opts.Auth)
	if err != nil {
		return nil, err
	}
	path := "/services/create?" + queryString(opts)
	resp, err := c.do(http.MethodPost, path, doOptions{
		headers:   headers,
		data:      opts.ServiceSpec,
		forceJSON: true,
		context:   opts.Context,
	})
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	var service swarm.Service
	if err := json.NewDecoder(resp.Body).Decode(&service); err != nil {
		return nil, err
	}
	return &service, nil
}

// RemoveServiceOptions encapsulates options to remove a service.
//
// See https://goo.gl/Tqrtya for more details.
type RemoveServiceOptions struct {
	ID      string `qs:"-"`
	Context context.Context
}

// RemoveService removes a service, returning an error in case of failure.
//
// See https://goo.gl/Tqrtya for more details.
func (c *Client) RemoveService(opts RemoveServiceOptions) error {
	path := "/services/" + opts.ID
	resp, err := c.do(http.MethodDelete, path, doOptions{context: opts.Context})
	if err != nil {
		if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
			return &NoSuchService{ID: opts.ID}
		}
		return err
	}
	resp.Body.Close()
	return nil
}

// UpdateServiceOptions specify parameters to the UpdateService function.
//
// See https://goo.gl/wu3MmS for more details.
type UpdateServiceOptions struct {
	Auth              AuthConfiguration `qs:"-"`
	swarm.ServiceSpec `qs:"-"`
	Context           context.Context
	Version           uint64
	Rollback          string
}

// UpdateService updates the service at ID with the options
//
// See https://goo.gl/wu3MmS for more details.
func (c *Client) UpdateService(id string, opts UpdateServiceOptions) error {
	headers, err := headersWithAuth(opts.Auth)
	if err != nil {
		return err
	}
	resp, err := c.do(http.MethodPost, "/services/"+id+"/update?"+queryString(opts), doOptions{
		headers:   headers,
		data:      opts.ServiceSpec,
		forceJSON: true,
		context:   opts.Context,
	})
	if err != nil {
		if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
			return &NoSuchService{ID: id}
		}
		return err
	}
	defer resp.Body.Close()
	return nil
}

// InspectService returns information about a service by its ID.
//
// See https://goo.gl/dHmr75 for more details.
func (c *Client) InspectService(id string) (*swarm.Service, error) {
	path := "/services/" + id
	resp, err := c.do(http.MethodGet, path, doOptions{})
	if err != nil {
		if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
			return nil, &NoSuchService{ID: id}
		}
		return nil, err
	}
	defer resp.Body.Close()
	var service swarm.Service
	if err := json.NewDecoder(resp.Body).Decode(&service); err != nil {
		return nil, err
	}
	return &service, nil
}

// ListServicesOptions specify parameters to the ListServices function.
//
// See https://goo.gl/DwvNMd for more details.
type ListServicesOptions struct {
	Filters map[string][]string
	Context context.Context
}

// ListServices returns a slice of services matching the given criteria.
//
// See https://goo.gl/DwvNMd for more details.
func (c *Client) ListServices(opts ListServicesOptions) ([]swarm.Service, error) {
	path := "/services?" + queryString(opts)
	resp, err := c.do(http.MethodGet, path, doOptions{context: opts.Context})
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	var services []swarm.Service
	if err := json.NewDecoder(resp.Body).Decode(&services); err != nil {
		return nil, err
	}
	return services, nil
}

// LogsServiceOptions represents the set of options used when getting logs from a
// service.
type LogsServiceOptions struct {
	Context           context.Context
	Service           string        `qs:"-"`
	OutputStream      io.Writer     `qs:"-"`
	ErrorStream       io.Writer     `qs:"-"`
	InactivityTimeout time.Duration `qs:"-"`
	Tail              string
	Since             int64

	// Use raw terminal? Usually true when the container contains a TTY.
	RawTerminal bool `qs:"-"`
	Follow      bool
	Stdout      bool
	Stderr      bool
	Timestamps  bool
	Details     bool
}

// GetServiceLogs gets stdout and stderr logs from the specified service.
//
// When LogsServiceOptions.RawTerminal is set to false, go-dockerclient will multiplex
// the streams and send the containers stdout to LogsServiceOptions.OutputStream, and
// stderr to LogsServiceOptions.ErrorStream.
//
// When LogsServiceOptions.RawTerminal is true, callers will get the raw stream on
// LogsServiceOptions.OutputStream.
func (c *Client) GetServiceLogs(opts LogsServiceOptions) error {
	if opts.Service == "" {
		return &NoSuchService{ID: opts.Service}
	}
	if opts.Tail == "" {
		opts.Tail = "all"
	}
	path := "/services/" + opts.Service + "/logs?" + queryString(opts)
	return c.stream(http.MethodGet, path, streamOptions{
		setRawTerminal:    opts.RawTerminal,
		stdout:            opts.OutputStream,
		stderr:            opts.ErrorStream,
		inactivityTimeout: opts.InactivityTimeout,
		context:           opts.Context,
	})
}
<commit_msg>swarm_service: add Status to ListServicesOptions<commit_after>// Copyright 2016 go-dockerclient authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package docker

import (
	"context"
	"encoding/json"
	"io"
	"net/http"
	"time"

	"github.com/docker/docker/api/types/swarm"
)

// NoSuchService is the error returned when a given service does not exist.
type NoSuchService struct {
	ID  string
	Err error
}

func (err *NoSuchService) Error() string {
	if err.Err != nil {
		return err.Err.Error()
	}
	return "No such service: " + err.ID
}

// CreateServiceOptions specify parameters to the CreateService function.
//
// See https://goo.gl/KrVjHz for more details.
type CreateServiceOptions struct {
	Auth AuthConfiguration `qs:"-"`
	swarm.ServiceSpec
	Context context.Context
}

// CreateService creates a new service, returning the service instance
// or an error in case of failure.
//
// See https://goo.gl/KrVjHz for more details.
func (c *Client) CreateService(opts CreateServiceOptions) (*swarm.Service, error) {
	headers, err := headersWithAuth(opts.Auth)
	if err != nil {
		return nil, err
	}
	path := "/services/create?" + queryString(opts)
	resp, err := c.do(http.MethodPost, path, doOptions{
		headers:   headers,
		data:      opts.ServiceSpec,
		forceJSON: true,
		context:   opts.Context,
	})
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	var service swarm.Service
	if err := json.NewDecoder(resp.Body).Decode(&service); err != nil {
		return nil, err
	}
	return &service, nil
}

// RemoveServiceOptions encapsulates options to remove a service.
//
// See https://goo.gl/Tqrtya for more details.
type RemoveServiceOptions struct {
	ID      string `qs:"-"`
	Context context.Context
}

// RemoveService removes a service, returning an error in case of failure.
//
// See https://goo.gl/Tqrtya for more details.
func (c *Client) RemoveService(opts RemoveServiceOptions) error {
	path := "/services/" + opts.ID
	resp, err := c.do(http.MethodDelete, path, doOptions{context: opts.Context})
	if err != nil {
		if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
			return &NoSuchService{ID: opts.ID}
		}
		return err
	}
	resp.Body.Close()
	return nil
}

// UpdateServiceOptions specify parameters to the UpdateService function.
//
// See https://goo.gl/wu3MmS for more details.
type UpdateServiceOptions struct {
	Auth              AuthConfiguration `qs:"-"`
	swarm.ServiceSpec `qs:"-"`
	Context           context.Context
	Version           uint64
	Rollback          string
}

// UpdateService updates the service at ID with the options
//
// See https://goo.gl/wu3MmS for more details.
func (c *Client) UpdateService(id string, opts UpdateServiceOptions) error {
	headers, err := headersWithAuth(opts.Auth)
	if err != nil {
		return err
	}
	resp, err := c.do(http.MethodPost, "/services/"+id+"/update?"+queryString(opts), doOptions{
		headers:   headers,
		data:      opts.ServiceSpec,
		forceJSON: true,
		context:   opts.Context,
	})
	if err != nil {
		if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
			return &NoSuchService{ID: id}
		}
		return err
	}
	defer resp.Body.Close()
	return nil
}

// InspectService returns information about a service by its ID.
//
// See https://goo.gl/dHmr75 for more details.
func (c *Client) InspectService(id string) (*swarm.Service, error) {
	path := "/services/" + id
	resp, err := c.do(http.MethodGet, path, doOptions{})
	if err != nil {
		if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
			return nil, &NoSuchService{ID: id}
		}
		return nil, err
	}
	defer resp.Body.Close()
	var service swarm.Service
	if err := json.NewDecoder(resp.Body).Decode(&service); err != nil {
		return nil, err
	}
	return &service, nil
}

// ListServicesOptions specify parameters to the ListServices function.
//
// See https://goo.gl/DwvNMd for more details.
type ListServicesOptions struct {
	Filters map[string][]string
	Status  bool
	Context context.Context
}

// ListServices returns a slice of services matching the given criteria.
//
// See https://goo.gl/DwvNMd for more details.
func (c *Client) ListServices(opts ListServicesOptions) ([]swarm.Service, error) {
	path := "/services?" + queryString(opts)
	resp, err := c.do(http.MethodGet, path, doOptions{context: opts.Context})
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	var services []swarm.Service
	if err := json.NewDecoder(resp.Body).Decode(&services); err != nil {
		return nil, err
	}
	return services, nil
}

// LogsServiceOptions represents the set of options used when getting logs from a
// service.
type LogsServiceOptions struct {
	Context           context.Context
	Service           string        `qs:"-"`
	OutputStream      io.Writer     `qs:"-"`
	ErrorStream       io.Writer     `qs:"-"`
	InactivityTimeout time.Duration `qs:"-"`
	Tail              string
	Since             int64

	// Use raw terminal? Usually true when the container contains a TTY.
	RawTerminal bool `qs:"-"`
	Follow      bool
	Stdout      bool
	Stderr      bool
	Timestamps  bool
	Details     bool
}

// GetServiceLogs gets stdout and stderr logs from the specified service.
//
// When LogsServiceOptions.RawTerminal is set to false, go-dockerclient will multiplex
// the streams and send the containers stdout to LogsServiceOptions.OutputStream, and
// stderr to LogsServiceOptions.ErrorStream.
//
// When LogsServiceOptions.RawTerminal is true, callers will get the raw stream on
// LogsServiceOptions.OutputStream.
func (c *Client) GetServiceLogs(opts LogsServiceOptions) error {
	if opts.Service == "" {
		return &NoSuchService{ID: opts.Service}
	}
	if opts.Tail == "" {
		opts.Tail = "all"
	}
	path := "/services/" + opts.Service + "/logs?" + queryString(opts)
	return c.stream(http.MethodGet, path, streamOptions{
		setRawTerminal:    opts.RawTerminal,
		stdout:            opts.OutputStream,
		stderr:            opts.ErrorStream,
		inactivityTimeout: opts.InactivityTimeout,
		context:           opts.Context,
	})
}
<|endoftext|>


<commit_before>package rest

import (
	"encoding/json"
	"log"
	"net/http"
	"os"
	"time"
)

type responseLogRecord struct {
	StatusCode   int
	ResponseTime *time.Duration
	HttpMethod   string
	RequestURI   string
}

func (self *ResourceHandler) logResponseRecord(record *responseLogRecord) {
	if self.EnableLogAsJson {
		b, err := json.Marshal(record)
		if err != nil {
			panic(err)
		}
		self.Logger.Printf("%s", b)
	} else {
		self.Logger.Printf("%d %v %s %s",
			record.StatusCode,
			record.ResponseTime,
			record.HttpMethod,
			record.RequestURI,
		)
	}
}

func (self *ResourceHandler) logWrapper(h http.HandlerFunc) http.HandlerFunc {

	// set a default Logger
	if self.Logger == nil {
		self.Logger = log.New(os.Stderr, "", log.LstdFlags)
	}

	return func(w http.ResponseWriter, r *http.Request) {

		// call the handler
		h(w, r)

		self.logResponseRecord(&responseLogRecord{
			self.env.getVar(r, "statusCode").(int),
			self.env.getVar(r, "elapsedTime").(*time.Duration),
			r.Method,
			r.URL.RequestURI(),
		})
	}
}
<commit_msg>Color logging<commit_after>package rest

import (
	"encoding/json"
	"log"
	"net/http"
	"os"
	"time"
)

type responseLogRecord struct {
	StatusCode   int
	ResponseTime *time.Duration
	HttpMethod   string
	RequestURI   string
}

func (self *ResourceHandler) logResponseRecord(record *responseLogRecord) {
	if self.EnableLogAsJson {
		b, err := json.Marshal(record)
		if err != nil {
			panic(err)
		}
		self.Logger.Printf("%s", b)
	} else {
		statusCodeColor := "0;32"
		if record.StatusCode >= 400 && record.StatusCode < 500 {
			statusCodeColor = "1;33"
		} else if record.StatusCode >= 500 {
			statusCodeColor = "0;31"
		}
		self.Logger.Printf("\033[%sm%d\033[0m \033[36;1m%.2fms\033[0m %s %s",
			statusCodeColor,
			record.StatusCode,
			float64(record.ResponseTime.Nanoseconds()/1e4)/100.0,
			record.HttpMethod,
			record.RequestURI,
		)
	}
}

func (self *ResourceHandler) logWrapper(h http.HandlerFunc) http.HandlerFunc {

	// set a default Logger
	if self.Logger == nil {
		self.Logger = log.New(os.Stderr, "", log.LstdFlags)
	}

	return func(w http.ResponseWriter, r *http.Request) {

		// call the handler
		h(w, r)

		self.logResponseRecord(&responseLogRecord{
			self.env.getVar(r, "statusCode").(int),
			self.env.getVar(r, "elapsedTime").(*time.Duration),
			r.Method,
			r.URL.RequestURI(),
		})
	}
}
<|endoftext|>


<commit_before>// Package log provide an easy to use logging package that supports level-based and asynchronized logging.
// It's designed to be used as a drop-in replacement of the standard log package
package log

import (
	"bytes"
	"fmt"
	"io"
	"net/http"
	"os"
	"path"
	"time"
)

const (
	LOG_LEVEL_TRACE = 0
	LOG_LEVEL_DEBUG = 1
	LOG_LEVEL_INFO  = 2
	LOG_LEVEL_WARN  = 3
	LOG_LEVEL_ERROR = 4
	LOG_LEVEL_FATAL = 5
)

type HTTPLogWriter struct {
	url string
}

type LogMessage struct {
	data []byte
}

const DEFAULT_QUEUE_SIZE = 100

type AsyncLogWriter struct {
	w       io.Writer
	queue   chan LogMessage
	closing bool
	closed  chan int
}

func NewAsyncLogWriter(w io.Writer, n int) *AsyncLogWriter {
	if n <= 0 {
		n = DEFAULT_QUEUE_SIZE
	}
	queue := make(chan LogMessage, n)

	aw := &AsyncLogWriter{
		queue:   queue,
		w:       w,
		closing: false,
		closed:  make(chan int),
	}

	go func(w *AsyncLogWriter) {
		for !w.closing {
			// process all queued messages
			for msg := range w.queue {
				_, err := w.w.Write(msg.data)
				if err != nil {
					// the writer failed to write the message somehow,
					// we just discard the message here, but other implementations
					// might try to resend the message
				}
			}
		}
		w.closed <- 1 // all messages are processed. ready to close
	}(aw)

	return aw
}

func (w *AsyncLogWriter) Close() {
	w.closing = true
	<-w.closed
}

func (w *AsyncLogWriter) Write(data []byte) (n int, err error) {
	w.queue <- LogMessage{data: data}
	return len(data), nil
}

type LogFormatter interface {
	Format(t time.Time, level int, message string) string
}

type Logger struct {
	level       int
	path        string
	fname       string
	writer      io.Writer
	writeCloser io.WriteCloser
	formatter   LogFormatter
}

func (w *HTTPLogWriter) Write(data []byte) (n int, err error) {
	resp, err := http.Post(w.url, "html/text", bytes.NewReader(data))
	if err != nil {
		return 0, err
	}
	defer resp.Body.Close()
	return len(data), err
}

// DefaultLogFormatter format log message in this format: "INFO: 2006-01-02T15:04:05 (UTC): log message..."
type DefaultLogFormatter struct {
}

func (f *DefaultLogFormatter) Format(t time.Time, level int, message string) string {
	timeStr := t.UTC().Format("2006-01-02T15:04:05 (MST)")
	return fmt.Sprintf("%s: %s: %s\n", loglevel2string(level), timeStr, message)
}

// New creates a new logger with the given writer
func New(w io.Writer, loglevel int) *Logger {
	return &Logger{
		level:     loglevel,
		writer:    w,
		formatter: &DefaultLogFormatter{},
	}
}

// NewHTTPLogger creates a logger that sends log to a http server
func NewHTTPLogger(url string, loglevel int) *Logger {
	return &Logger{
		level:     loglevel,
		writer:    NewAsyncLogWriter(&HTTPLogWriter{url: url}, DEFAULT_QUEUE_SIZE),
		formatter: &DefaultLogFormatter{},
	}
}

// NewFileLogger creates a new logger which writes logs to the specified logpath and filename
func NewFileLogger(logpath string, fname string, loglevel int) *Logger {

	// create the log directory if not exists
	err := os.MkdirAll(logpath, 0750)
	if err != nil {
		panic(err)
	}

	// use program name as log filename
	if fname == "" {
		fname = path.Base(os.Args[0])
	}
	filepath := fmt.Sprintf("%s/%s.log", logpath, fname)

	// open the log file
	file, err := os.OpenFile(filepath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0640)
	if err != nil {
		panic(err)
	}

	return &Logger{
		level:       loglevel,
		path:        logpath,
		fname:       fname,
		writeCloser: file,
		writer:      file,
		formatter:   &DefaultLogFormatter{},
	}
}

// SetLogLevel sets the current log level of the logger
func (logger *Logger) SetLogLevel(level int) {
	logger.level = level
}

// Close closes the writer of the logger.
func (logger *Logger) Close() {
	if logger.writeCloser != nil {
		logger.writeCloser.Close()
	}
}

// Writer returns current writer of the logger.
func (logger *Logger) Writer() io.Writer {
	return logger.writer
}

// Print logs a formatted message at LOG_LEVEL_INFO level
func (logger *Logger) Print(v ...interface{}) {
	s := fmt.Sprint(v...)
	msg := logger.formatter.Format(time.Now(), logger.level, s)
	if logger.Writer() != nil {
		logger.Writer().Write([]byte(msg))
	}
}

// Println logs a formatted message at LOG_LEVEL_INFO level
func (logger *Logger) Println(v ...interface{}) {
	s := fmt.Sprintln(v...)
	msg := logger.formatter.Format(time.Now(), logger.level, s)
	if logger.Writer() != nil {
		logger.Writer().Write([]byte(msg))
	}
}

// Println logs a formatted message at LOG_LEVEL_INFO level
func (logger *Logger) Printf(format string, v ...interface{}) {
	s := fmt.Sprintf(format, v...)
	msg := logger.formatter.Format(time.Now(), logger.level, s)
	if logger.Writer() != nil {
		logger.Writer().Write([]byte(msg))
	}
}

// Log logs a formatted message at the given log level
func (logger *Logger) Log(loglevel int, v ...interface{}) {
	if loglevel >= logger.level {
		s := fmt.Sprint(v...)
		msg := logger.formatter.Format(time.Now(), loglevel, s)
		if logger.Writer() != nil {
			logger.Writer().Write([]byte(msg))
		}
	}
}

// Logf logs a formatted message at the given log level
func (logger *Logger) Logf(loglevel int, format string, v ...interface{}) {
	if loglevel >= logger.level {
		s := fmt.Sprintf(format, v...)
		msg := logger.formatter.Format(time.Now(), loglevel, s)
		if logger.Writer() != nil {
			logger.Writer().Write([]byte(msg))
		}
	}
}

// Logln logs a formatted message at the given log level
func (logger *Logger) Logln(loglevel int, v ...interface{}) {
	if loglevel >= logger.level {
		s := fmt.Sprintln(v...)
		msg := logger.formatter.Format(time.Now(), loglevel, s)
		if logger.Writer() != nil {
			logger.Writer().Write([]byte(msg))
		}
	}
}

// Trace logs a formatted message at log level: LOG_LEVEL_TRACE
func (logger *Logger) Trace(v ...interface{}) {
	logger.Log(LOG_LEVEL_TRACE, v...)
}

// Tracef logs a formatted message at log level: LOG_LEVEL_TRACE
func (logger *Logger) Tracef(fmt string, v ...interface{}) {
	logger.Logf(LOG_LEVEL_TRACE, fmt, v...)
}

// Tracef logs a formatted message at log level: LOG_LEVEL_TRACE
func (logger *Logger) Traceln(v ...interface{}) {
	logger.Logln(LOG_LEVEL_TRACE, v...)
}

// Debug logs a formatted message at log level: LOG_LEVEL_DEBUG
func (logger *Logger) Debug(v ...interface{}) {
	logger.Log(LOG_LEVEL_DEBUG, v...)
}

// Debugf logs a formatted message at log level: LOG_LEVEL_DEBUG
func (logger *Logger) Debugf(format string, v ...interface{}) {
	logger.Logf(LOG_LEVEL_DEBUG, format, v...)
}

// Debugln logs a formatted message at log level: LOG_LEVEL_DEBUG
func (logger *Logger) Debugln(v ...interface{}) {
	logger.Logln(LOG_LEVEL_DEBUG, v...)
}

// Info logs a formatted message at log level: LOG_LEVEL_INFO
func (logger *Logger) Info(v ...interface{}) {
	logger.Log(LOG_LEVEL_INFO, v...)
}

// Infof logs a formatted message at log level: LOG_LEVEL_INFO
func (logger *Logger) Infof(format string, v ...interface{}) {
	logger.Logf(LOG_LEVEL_INFO, format, v...)
}

// Infoln logs a formatted message at log level: LOG_LEVEL_INFO
func (logger *Logger) Infoln(v ...interface{}) {
	logger.Logln(LOG_LEVEL_INFO, v...)
}

// Warn logs a formatted message at log level: LOG_LEVEL_WARN
func (logger *Logger) Warn(v ...interface{}) {
	logger.Log(LOG_LEVEL_WARN, v...)
}

// Warnf logs a formatted message at log level: LOG_LEVEL_WARN
func (logger *Logger) Warnf(format string, v ...interface{}) {
	logger.Logf(LOG_LEVEL_WARN, format, v...)
}

// Warnln logs a formatted message at log level: LOG_LEVEL_WARN
func (logger *Logger) Warnln(v ...interface{}) {
	logger.Logln(LOG_LEVEL_WARN, v...)
}

// Error logs a formatted message at log level: LOG_LEVEL_ERROR
func (logger *Logger) Error(v ...interface{}) {
	logger.Log(LOG_LEVEL_ERROR, v...)
}

// Errorf logs a formatted message at log level: LOG_LEVEL_ERROR
func (logger *Logger) Errorf(format string, v ...interface{}) {
	logger.Logf(LOG_LEVEL_ERROR, format, v...)
}

// Errorln logs a formatted message at log level: LOG_LEVEL_ERROR
func (logger *Logger) Errorln(v ...interface{}) {
	logger.Logln(LOG_LEVEL_ERROR, v...)
}

// Fatal logs a formatted message at log level: LOG_LEVEL_FATAL then calls os.Exit(1)
func (logger *Logger) Fatal(v ...interface{}) {
	logger.Log(LOG_LEVEL_FATAL, v...)
	os.Exit(1)
}

// Fatalf logs a formatted message at log level: LOG_LEVEL_FATAL then calls os.Exit(1)
func (logger *Logger) Fatalf(format string, v ...interface{}) {
	logger.Logf(LOG_LEVEL_FATAL, format, v...)
	os.Exit(1)
}

// Panic logs a formatted message at log level: LOG_LEVEL_FATAL then calls os.Exit(1)
func (logger *Logger) Fatalln(v ...interface{}) {
	logger.Logln(LOG_LEVEL_FATAL, v...)
	os.Exit(1)
}

// Panic logs a message at log level: LOG_LEVEL_FATAL then calls panic()
func (logger *Logger) Panic(v ...interface{}) {
	logger.Log(LOG_LEVEL_FATAL, v...)
	panic(nil)
}

// Panicf logs a formatted message at log level: LOG_LEVEL_FATAL then calls panic()
func (logger *Logger) Panicf(format string, v ...interface{}) {
	logger.Logf(LOG_LEVEL_FATAL, format, v...)
	panic(nil)
}

// Panicln logs a formatted message at log level: LOG_LEVEL_FATAL then calls panic()
func (logger *Logger) Panicln(v ...interface{}) {
	logger.Logln(LOG_LEVEL_FATAL, v...)
	panic(nil)
}

func loglevel2string(level int) string {
	switch level {
	case LOG_LEVEL_TRACE:
		return "TRACE"
	case LOG_LEVEL_DEBUG:
		return "DEBUG"
	case LOG_LEVEL_INFO:
		return "INFO"
	case LOG_LEVEL_WARN:
		return "WARN"
	case LOG_LEVEL_ERROR:
		return "ERROR"
	case LOG_LEVEL_FATAL:
		return "FATAL"
	default:
		return "Unknown"
	}
}
<commit_msg>Add String2LogLevel and LogLevel2String functions<commit_after>// Package log provide an easy to use logging package that supports level-based and asynchronized logging.
// It's designed to be used as a drop-in replacement of the standard log package
package log

import (
	"bytes"
	"fmt"
	"io"
	"net/http"
	"os"
	"path"
	"strings"
	"time"
)

const (
	LOG_LEVEL_TRACE = 0
	LOG_LEVEL_DEBUG = 1
	LOG_LEVEL_INFO  = 2
	LOG_LEVEL_WARN  = 3
	LOG_LEVEL_ERROR = 4
	LOG_LEVEL_FATAL = 5
)

type HTTPLogWriter struct {
	url string
}

type LogMessage struct {
	data []byte
}

const DEFAULT_QUEUE_SIZE = 100

type AsyncLogWriter struct {
	w       io.Writer
	queue   chan LogMessage
	closing bool
	closed  chan int
}

func NewAsyncLogWriter(w io.Writer, n int) *AsyncLogWriter {
	if n <= 0 {
		n = DEFAULT_QUEUE_SIZE
	}
	queue := make(chan LogMessage, n)

	aw := &AsyncLogWriter{
		queue:   queue,
		w:       w,
		closing: false,
		closed:  make(chan int),
	}

	go func(w *AsyncLogWriter) {
		for !w.closing {
			// process all queued messages
			for msg := range w.queue {
				_, err := w.w.Write(msg.data)
				if err != nil {
					// the writer failed to write the message somehow,
					// we just discard the message here, but other implementations
					// might try to resend the message
				}
			}
		}
		w.closed <- 1 // all messages are processed. ready to close
	}(aw)

	return aw
}

func (w *AsyncLogWriter) Close() {
	w.closing = true
	<-w.closed
}

func (w *AsyncLogWriter) Write(data []byte) (n int, err error) {
	w.queue <- LogMessage{data: data}
	return len(data), nil
}

type LogFormatter interface {
	Format(t time.Time, level int, message string) string
}

type Logger struct {
	level       int
	path        string
	fname       string
	writer      io.Writer
	writeCloser io.WriteCloser
	formatter   LogFormatter
}

func (w *HTTPLogWriter) Write(data []byte) (n int, err error) {
	resp, err := http.Post(w.url, "html/text", bytes.NewReader(data))
	if err != nil {
		return 0, err
	}
	defer resp.Body.Close()
	return len(data), err
}

// DefaultLogFormatter format log message in this format: "INFO: 2006-01-02T15:04:05 (UTC): log message..."
type DefaultLogFormatter struct {
}

func (f *DefaultLogFormatter) Format(t time.Time, level int, message string) string {
	timeStr := t.UTC().Format("2006-01-02T15:04:05 (MST)")
	return fmt.Sprintf("%s: %s: %s\n", LogLevel2String(level), timeStr, message)
}

// New creates a new logger with the given writer
func New(w io.Writer, loglevel int) *Logger {
	return &Logger{
		level:     loglevel,
		writer:    w,
		formatter: &DefaultLogFormatter{},
	}
}

// NewHTTPLogger creates a logger that sends log to a http server
func NewHTTPLogger(url string, loglevel int) *Logger {
	return &Logger{
		level:     loglevel,
		writer:    NewAsyncLogWriter(&HTTPLogWriter{url: url}, DEFAULT_QUEUE_SIZE),
		formatter: &DefaultLogFormatter{},
	}
}

// NewFileLogger creates a new logger which writes logs to the specified logpath and filename
func NewFileLogger(logpath string, fname string, loglevel int) *Logger {

	// create the log directory if not exists
	err := os.MkdirAll(logpath, 0750)
	if err != nil {
		panic(err)
	}

	// use program name as log filename
	if fname == "" {
		fname = path.Base(os.Args[0])
	}
	filepath := fmt.Sprintf("%s/%s.log", logpath, fname)

	// open the log file
	file, err := os.OpenFile(filepath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0640)
	if err != nil {
		panic(err)
	}

	return &Logger{
		level:       loglevel,
		path:        logpath,
		fname:       fname,
		writeCloser: file,
		writer:      file,
		formatter:   &DefaultLogFormatter{},
	}
}

// SetLogLevel sets the current log level of the logger
func (logger *Logger) SetLogLevel(level int) {
	logger.level = level
}

// Close closes the writer of the logger.
func (logger *Logger) Close() {
	if logger.writeCloser != nil {
		logger.writeCloser.Close()
	}
}

// Writer returns current writer of the logger.
func (logger *Logger) Writer() io.Writer {
	return logger.writer
}

// Print logs a formatted message at LOG_LEVEL_INFO level
func (logger *Logger) Print(v ...interface{}) {
	s := fmt.Sprint(v...)
	msg := logger.formatter.Format(time.Now(), logger.level, s)
	if logger.Writer() != nil {
		logger.Writer().Write([]byte(msg))
	}
}

// Println logs a formatted message at LOG_LEVEL_INFO level
func (logger *Logger) Println(v ...interface{}) {
	s := fmt.Sprintln(v...)
	msg := logger.formatter.Format(time.Now(), logger.level, s)
	if logger.Writer() != nil {
		logger.Writer().Write([]byte(msg))
	}
}

// Println logs a formatted message at LOG_LEVEL_INFO level
func (logger *Logger) Printf(format string, v ...interface{}) {
	s := fmt.Sprintf(format, v...)
	msg := logger.formatter.Format(time.Now(), logger.level, s)
	if logger.Writer() != nil {
		logger.Writer().Write([]byte(msg))
	}
}

// Log logs a formatted message at the given log level
func (logger *Logger) Log(loglevel int, v ...interface{}) {
	if loglevel >= logger.level {
		s := fmt.Sprint(v...)
		msg := logger.formatter.Format(time.Now(), loglevel, s)
		if logger.Writer() != nil {
			logger.Writer().Write([]byte(msg))
		}
	}
}

// Logf logs a formatted message at the given log level
func (logger *Logger) Logf(loglevel int, format string, v ...interface{}) {
	if loglevel >= logger.level {
		s := fmt.Sprintf(format, v...)
		msg := logger.formatter.Format(time.Now(), loglevel, s)
		if logger.Writer() != nil {
			logger.Writer().Write([]byte(msg))
		}
	}
}

// Logln logs a formatted message at the given log level
func (logger *Logger) Logln(loglevel int, v ...interface{}) {
	if loglevel >= logger.level {
		s := fmt.Sprintln(v...)
		msg := logger.formatter.Format(time.Now(), loglevel, s)
		if logger.Writer() != nil {
			logger.Writer().Write([]byte(msg))
		}
	}
}

// Trace logs a formatted message at log level: LOG_LEVEL_TRACE
func (logger *Logger) Trace(v ...interface{}) {
	logger.Log(LOG_LEVEL_TRACE, v...)
}

// Tracef logs a formatted message at log level: LOG_LEVEL_TRACE
func (logger *Logger) Tracef(fmt string, v ...interface{}) {
	logger.Logf(LOG_LEVEL_TRACE, fmt, v...)
}

// Tracef logs a formatted message at log level: LOG_LEVEL_TRACE
func (logger *Logger) Traceln(v ...interface{}) {
	logger.Logln(LOG_LEVEL_TRACE, v...)
}

// Debug logs a formatted message at log level: LOG_LEVEL_DEBUG
func (logger *Logger) Debug(v ...interface{}) {
	logger.Log(LOG_LEVEL_DEBUG, v...)
}

// Debugf logs a formatted message at log level: LOG_LEVEL_DEBUG
func (logger *Logger) Debugf(format string, v ...interface{}) {
	logger.Logf(LOG_LEVEL_DEBUG, format, v...)
}

// Debugln logs a formatted message at log level: LOG_LEVEL_DEBUG
func (logger *Logger) Debugln(v ...interface{}) {
	logger.Logln(LOG_LEVEL_DEBUG, v...)
}

// Info logs a formatted message at log level: LOG_LEVEL_INFO
func (logger *Logger) Info(v ...interface{}) {
	logger.Log(LOG_LEVEL_INFO, v...)
}

// Infof logs a formatted message at log level: LOG_LEVEL_INFO
func (logger *Logger) Infof(format string, v ...interface{}) {
	logger.Logf(LOG_LEVEL_INFO, format, v...)
}

// Infoln logs a formatted message at log level: LOG_LEVEL_INFO
func (logger *Logger) Infoln(v ...interface{}) {
	logger.Logln(LOG_LEVEL_INFO, v...)
}

// Warn logs a formatted message at log level: LOG_LEVEL_WARN
func (logger *Logger) Warn(v ...interface{}) {
	logger.Log(LOG_LEVEL_WARN, v...)
}

// Warnf logs a formatted message at log level: LOG_LEVEL_WARN
func (logger *Logger) Warnf(format string, v ...interface{}) {
	logger.Logf(LOG_LEVEL_WARN, format, v...)
}

// Warnln logs a formatted message at log level: LOG_LEVEL_WARN
func (logger *Logger) Warnln(v ...interface{}) {
	logger.Logln(LOG_LEVEL_WARN, v...)
}

// Error logs a formatted message at log level: LOG_LEVEL_ERROR
func (logger *Logger) Error(v ...interface{}) {
	logger.Log(LOG_LEVEL_ERROR, v...)
}

// Errorf logs a formatted message at log level: LOG_LEVEL_ERROR
func (logger *Logger) Errorf(format string, v ...interface{}) {
	logger.Logf(LOG_LEVEL_ERROR, format, v...)
}

// Errorln logs a formatted message at log level: LOG_LEVEL_ERROR
func (logger *Logger) Errorln(v ...interface{}) {
	logger.Logln(LOG_LEVEL_ERROR, v...)
}

// Fatal logs a formatted message at log level: LOG_LEVEL_FATAL then calls os.Exit(1)
func (logger *Logger) Fatal(v ...interface{}) {
	logger.Log(LOG_LEVEL_FATAL, v...)
	os.Exit(1)
}

// Fatalf logs a formatted message at log level: LOG_LEVEL_FATAL then calls os.Exit(1)
func (logger *Logger) Fatalf(format string, v ...interface{}) {
	logger.Logf(LOG_LEVEL_FATAL, format, v...)
	os.Exit(1)
}

// Panic logs a formatted message at log level: LOG_LEVEL_FATAL then calls os.Exit(1)
func (logger *Logger) Fatalln(v ...interface{}) {
	logger.Logln(LOG_LEVEL_FATAL, v...)
	os.Exit(1)
}

// Panic logs a message at log level: LOG_LEVEL_FATAL then calls panic()
func (logger *Logger) Panic(v ...interface{}) {
	logger.Log(LOG_LEVEL_FATAL, v...)
	panic(nil)
}

// Panicf logs a formatted message at log level: LOG_LEVEL_FATAL then calls panic()
func (logger *Logger) Panicf(format string, v ...interface{}) {
	logger.Logf(LOG_LEVEL_FATAL, format, v...)
	panic(nil)
}

// Panicln logs a formatted message at log level: LOG_LEVEL_FATAL then calls panic()
func (logger *Logger) Panicln(v ...interface{}) {
	logger.Logln(LOG_LEVEL_FATAL, v...)
	panic(nil)
}

func LogLevel2String(level int) string {
	switch level {
	case LOG_LEVEL_TRACE:
		return "TRACE"
	case LOG_LEVEL_DEBUG:
		return "DEBUG"
	case LOG_LEVEL_INFO:
		return "INFO"
	case LOG_LEVEL_WARN:
		return "WARN"
	case LOG_LEVEL_ERROR:
		return "ERROR"
	case LOG_LEVEL_FATAL:
		return "FATAL"
	default:
		return "Unknown"
	}
}

func String2LogLevel(str string) int {
	str = strings.ToUpper(str)
	switch str {
	case "TRACE":
		return LOG_LEVEL_TRACE
	case "DEBUG":
		return LOG_LEVEL_DEBUG
	case "INFO":
		return LOG_LEVEL_INFO
	case "WARN":
		return LOG_LEVEL_WARN
	case "ERROR":
		return LOG_LEVEL_WARN
	case "FATAL":
		return LOG_LEVEL_FATAL
	default:
		return -1
	}
}
<|endoftext|>


<commit_before>/*
  Copyright (c) 2014, YU HengChun
  All rights reserved.

  Redistribution and use in source and binary forms, with or without modification,
  are permitted provided that the following conditions are met:

  * Redistributions of source code must retain the above copyright notice, this
    list of conditions and the following disclaimer.

  * Redistributions in binary form must reproduce the above copyright notice, this
    list of conditions and the following disclaimer in the documentation and/or
    other materials provided with the distribution.

  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
  ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
  ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

// few code fork go log.
package log

import (
	"fmt"
	"io"
	"os"
	"runtime"
	"strconv"
	"sync"
	"time"
)

const (
	Ldate = 1 << iota
	Ltime
	Lmicroseconds
	Llongfile
	Lshortfile
	LstdFlags = Ldate | Ltime
)

// dont change order
const (
	LZero = -iota
	LFatal
	LPanic // recover panic
	LAlert
	LError
	LReport
	LNotify
	LInfo
	LDebug
	nr_levels
)

var levelsName [-nr_levels - 1]string

func init() {
	levelsName[-LFatal-1] = "[F]"
	levelsName[-LPanic-1] = "[P]"
	levelsName[-LAlert-1] = "[A]"
	levelsName[-LError-1] = "[E]"
	levelsName[-LReport-1] = "[R]"
	levelsName[-LNotify-1] = "[N]"
	levelsName[-LInfo-1] = "[I]"
	levelsName[-LDebug-1] = "[D]"
}

// dont change order
const (
	MODE_EQUAL     = -iota - 100 // equal level mode
	MODE_NONE_NAME               // dont write default level name
	MODE_DONT_EXIT               // dont exec os.Exit when Fatal
	MODE_DONT_PANIC
	MODE_RECOVER
)

type nullWriter struct{}

func (f *nullWriter) Write(p []byte) (int, error) {
	return len(p), nil
}

type BaseLogger interface {
	Fatal(v ...interface{})
	Fatalf(format string, v ...interface{})
	Panic(v ...interface{})
	Panicf(format string, v ...interface{})
	Alert(v ...interface{})
	Alertf(format string, v ...interface{})
	Error(v ...interface{})
	Errorf(format string, v ...interface{})
	Report(v ...interface{})
	Reportf(format string, v ...interface{})
	Notify(v ...interface{})
	Notifyf(format string, v ...interface{})
	Info(v ...interface{})
	Infof(format string, v ...interface{})
	Debug(v ...interface{})
	Debugf(format string, v ...interface{})
}

type Logger interface {
	BaseLogger
	io.ReaderFrom
	Write([]byte) (int, error)
	Print(v ...interface{})
	Printf(format string, v ...interface{})
	Output(calldepth int, s string, optionLevel ...int) error
}

var _ Logger = &logger{}

type logger struct {
	mu     sync.Mutex // ensures atomic writes; protects the following fields
	prefix string     // prefix to write at beginning of each line
	flag   int        // properties
	out    io.Writer  // destination for output
	buf    []byte     // for accumulating text to write

	level     int
	equal     bool
	noneName  bool
	dontExit  bool
	dontPanic bool
	recover   bool
}

// Cheap integer to fixed-width decimal ASCII.  Give a negative width to avoid zero-padding.
// Knows the buffer has capacity.
func itoa(buf *[]byte, i int, wid int) {
	var u uint = uint(i)
	if u == 0 && wid <= 1 {
		*buf = append(*buf, '0')
		return
	}

	// Assemble decimal in reverse order.
	var b [32]byte
	bp := len(b)
	for ; u > 0 || wid > 0; u /= 10 {
		bp--
		wid--
		b[bp] = byte(u%10) + '0'
	}
	*buf = append(*buf, b[bp:]...)
}

func (l *logger) formatHeader(buf *[]byte, t time.Time, file string, line, level int) {
	if len(l.prefix) != 0 {
		*buf = append(*buf, l.prefix...)
		if len(l.prefix) != 0 {
			*buf = append(*buf, ' ')
		}
	}

	if !l.noneName && level > nr_levels && level <= 0 {
		*buf = append(*buf, levelsName[-level]...)
		*buf = append(*buf, ' ')
	}

	if l.flag&(Ldate|Ltime|Lmicroseconds) != 0 {
		if l.flag&Ldate != 0 {
			year, month, day := t.Date()
			itoa(buf, year, 4)
			*buf = append(*buf, '-')
			itoa(buf, int(month), 2)
			*buf = append(*buf, '-')
			itoa(buf, day, 2)
			*buf = append(*buf, ' ')
		}
		if l.flag&(Ltime|Lmicroseconds) != 0 {
			hour, min, sec := t.Clock()
			itoa(buf, hour, 2)
			*buf = append(*buf, ':')
			itoa(buf, min, 2)
			*buf = append(*buf, ':')
			itoa(buf, sec, 2)
			if l.flag&Lmicroseconds != 0 {
				*buf = append(*buf, '.')
				itoa(buf, t.Nanosecond()/1e3, 6)
			}
			*buf = append(*buf, ' ')
		}
	}
	if l.flag&(Lshortfile|Llongfile) != 0 {
		if l.flag&Lshortfile != 0 {
			short := file
			for i := len(file) - 1; i > 0; i-- {
				if file[i] == '/' {
					short = file[i+1:]
					break
				}
			}
			file = short
		}
		*buf = append(*buf, '<')
		*buf = append(*buf, file...)
		*buf = append(*buf, ':')
		itoa(buf, line, -1)
		*buf = append(*buf, `> `...)
	}
}

func printf(format string, v []interface{}) string {
	if len(format) == 0 {
		return fmt.Sprint(v...)
	} else {
		return fmt.Sprintf(format, v...)
	}
}

func (l *logger) Output(calldepth int, s string, optionLevel ...int) (err error) {
	level := LZero
	if len(optionLevel) != 0 {
		level = optionLevel[0]
	}
	if level == LZero || l.equal && level == l.level || !l.equal && level > l.level {
		level++
	} else {
		return
	}
	now := time.Now() // get this early.
	var file string
	var line int
	if len(s) != 0 {
		s = strconv.Quote(s)
	}
	if l.flag&(Lshortfile|Llongfile) != 0 {
		var ok bool
		_, file, line, ok = runtime.Caller(calldepth)
		if !ok {
			file = "???"
			line = 0
		}
	}
	l.mu.Lock()
	defer func() {
		l.mu.Unlock()
		if l.recover {
			_ = recover() // ignore panic
		}
	}()

	l.buf = l.buf[:0]

	l.formatHeader(&l.buf, now, file, line, level)

	l.buf = append(l.buf, s...)
	if len(s) > 0 && s[len(s)-1] != '\n' {
		l.buf = append(l.buf, '\n')
	}
	n := 0
	count := 0
	for len(l.buf) != 0 && (err == nil || err == io.ErrShortWrite) && count < 10 {
		n, err = l.out.Write(l.buf)
		l.buf = l.buf[n:]
		if err == io.ErrShortWrite {
			count++
		}
	}
	return
}

func (l *logger) ReadFrom(src Reader) (n int64, err error) {
	l.mu.Lock()
	defer func() {
		l.mu.Unlock()
		if l.recover {
			_ = recover() // ignore panic
		}
	}()
	return io.Copy(l.out, src)
}

func (l *logger) Write(p []byte) (n int, err error) {
	l.mu.Lock()
	defer func() {
		l.mu.Unlock()
		if l.recover {
			_ = recover() // ignore panic
		}
	}()
	wBytes := 0
	count := 0
	for len(p) != 0 && (err == nil || err == io.ErrShortWrite) && count < 10 {
		wBytes, err = l.out.Write(p)
		n += wBytes
		p = p[n:]
		if err == io.ErrShortWrite {
			count++
		}
	}
	return
}

func (l *logger) Print(v ...interface{}) {
	l.Output(2, printf("", v), LZero)
}

func (l *logger) Printf(format string, v ...interface{}) {
	l.Output(2, printf(format, v), LZero)
}

func (l *logger) Debug(v ...interface{}) {
	l.Output(2, printf("", v), LDebug)
}

func (l *logger) Debugf(format string, v ...interface{}) {
	l.Output(2, printf(format, v), LDebug)
}

func (l *logger) Info(v ...interface{}) {
	l.Output(2, printf("", v), LInfo)
}

func (l *logger) Infof(format string, v ...interface{}) {
	l.Output(2, printf(format, v), LInfo)
}

func (l *logger) Notify(v ...interface{}) {
	l.Output(2, printf("", v), LNotify)
}

func (l *logger) Notifyf(format string, v ...interface{}) {
	l.Output(2, printf(format, v), LNotify)
}

func (l *logger) Report(v ...interface{}) {
	l.Output(2, printf("", v), LReport)
}

func (l *logger) Reportf(format string, v ...interface{}) {
	l.Output(2, printf(format, v), LReport)
}

func (l *logger) Error(v ...interface{}) {
	l.Output(2, printf("", v), LError)
}

func (l *logger) Errorf(format string, v ...interface{}) {
	l.Output(2, printf(format, v), LError)
}

func (l *logger) Alert(v ...interface{}) {
	l.Output(2, printf("", v), LAlert)
}

func (l *logger) Alertf(format string, v ...interface{}) {
	l.Output(2, printf(format, v), LAlert)
}

func (l *logger) Panic(v ...interface{}) {
	l.Output(2, printf("", v), LPanic)
	if !l.dontPanic {
		panic(v)
	}
}

func (l *logger) Panicf(format string, v ...interface{}) {
	l.Output(2, printf(format, v), LPanic)
	if !l.dontPanic {
		panic(v)
	}
}

func (l *logger) Fatal(v ...interface{}) {
	l.Output(2, printf("", v), LFatal)
	if !l.dontExit {
		os.Exit(1)
	}
}

func (l *logger) Fatalf(format string, v ...interface{}) {
	l.Output(2, printf(format, v), LFatal)
	if !l.dontExit {
		os.Exit(1)
	}
}

func New(writer io.Writer, prefix string, flags ...int) Logger {
	ret := new(logger)
	ret.level = nr_levels
	hasflags := false
	for _, flag := range flags {
		switch flag {
		case MODE_EQUAL:
			ret.equal = true
		case MODE_NONE_NAME:
			ret.noneName = true
		case MODE_DONT_EXIT:
			ret.dontExit = true
		case MODE_DONT_PANIC:
			ret.dontPanic = true
		case MODE_RECOVER:
			ret.recover = true
		default:

			if flag >= 0 {
				hasflags = true
				ret.flag = ret.flag | flag
			} else {
				ret.level = flag
			}
		}
	}
	// defaults to LstdFlags.
	if !hasflags {
		ret.flag = LstdFlags
	}

	if ret.level <= nr_levels {
		ret.level = nr_levels + 1
	}

	ret.prefix = prefix
	ret.out = writer
	return ret
}
<commit_msg>fix ReadFrom<commit_after>/*
  Copyright (c) 2014, YU HengChun
  All rights reserved.

  Redistribution and use in source and binary forms, with or without modification,
  are permitted provided that the following conditions are met:

  * Redistributions of source code must retain the above copyright notice, this
    list of conditions and the following disclaimer.

  * Redistributions in binary form must reproduce the above copyright notice, this
    list of conditions and the following disclaimer in the documentation and/or
    other materials provided with the distribution.

  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
  ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
  ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

// few code fork go log.
package log

import (
	"fmt"
	"io"
	"os"
	"runtime"
	"strconv"
	"sync"
	"time"
)

const (
	Ldate = 1 << iota
	Ltime
	Lmicroseconds
	Llongfile
	Lshortfile
	LstdFlags = Ldate | Ltime
)

// dont change order
const (
	LZero = -iota
	LFatal
	LPanic // recover panic
	LAlert
	LError
	LReport
	LNotify
	LInfo
	LDebug
	nr_levels
)

var levelsName [-nr_levels - 1]string

func init() {
	levelsName[-LFatal-1] = "[F]"
	levelsName[-LPanic-1] = "[P]"
	levelsName[-LAlert-1] = "[A]"
	levelsName[-LError-1] = "[E]"
	levelsName[-LReport-1] = "[R]"
	levelsName[-LNotify-1] = "[N]"
	levelsName[-LInfo-1] = "[I]"
	levelsName[-LDebug-1] = "[D]"
}

// dont change order
const (
	MODE_EQUAL     = -iota - 100 // equal level mode
	MODE_NONE_NAME               // dont write default level name
	MODE_DONT_EXIT               // dont exec os.Exit when Fatal
	MODE_DONT_PANIC
	MODE_RECOVER
)

type nullWriter struct{}

func (f *nullWriter) Write(p []byte) (int, error) {
	return len(p), nil
}

type BaseLogger interface {
	Fatal(v ...interface{})
	Fatalf(format string, v ...interface{})
	Panic(v ...interface{})
	Panicf(format string, v ...interface{})
	Alert(v ...interface{})
	Alertf(format string, v ...interface{})
	Error(v ...interface{})
	Errorf(format string, v ...interface{})
	Report(v ...interface{})
	Reportf(format string, v ...interface{})
	Notify(v ...interface{})
	Notifyf(format string, v ...interface{})
	Info(v ...interface{})
	Infof(format string, v ...interface{})
	Debug(v ...interface{})
	Debugf(format string, v ...interface{})
}

type Logger interface {
	BaseLogger
	io.ReaderFrom
	Write([]byte) (int, error)
	Print(v ...interface{})
	Printf(format string, v ...interface{})
	Output(calldepth int, s string, optionLevel ...int) error
}

var _ Logger = &logger{}

type logger struct {
	mu     sync.Mutex // ensures atomic writes; protects the following fields
	prefix string     // prefix to write at beginning of each line
	flag   int        // properties
	out    io.Writer  // destination for output
	buf    []byte     // for accumulating text to write

	level     int
	equal     bool
	noneName  bool
	dontExit  bool
	dontPanic bool
	recover   bool
}

// Cheap integer to fixed-width decimal ASCII.  Give a negative width to avoid zero-padding.
// Knows the buffer has capacity.
func itoa(buf *[]byte, i int, wid int) {
	var u uint = uint(i)
	if u == 0 && wid <= 1 {
		*buf = append(*buf, '0')
		return
	}

	// Assemble decimal in reverse order.
	var b [32]byte
	bp := len(b)
	for ; u > 0 || wid > 0; u /= 10 {
		bp--
		wid--
		b[bp] = byte(u%10) + '0'
	}
	*buf = append(*buf, b[bp:]...)
}

func (l *logger) formatHeader(buf *[]byte, t time.Time, file string, line, level int) {
	if len(l.prefix) != 0 {
		*buf = append(*buf, l.prefix...)
		if len(l.prefix) != 0 {
			*buf = append(*buf, ' ')
		}
	}

	if !l.noneName && level > nr_levels && level <= 0 {
		*buf = append(*buf, levelsName[-level]...)
		*buf = append(*buf, ' ')
	}

	if l.flag&(Ldate|Ltime|Lmicroseconds) != 0 {
		if l.flag&Ldate != 0 {
			year, month, day := t.Date()
			itoa(buf, year, 4)
			*buf = append(*buf, '-')
			itoa(buf, int(month), 2)
			*buf = append(*buf, '-')
			itoa(buf, day, 2)
			*buf = append(*buf, ' ')
		}
		if l.flag&(Ltime|Lmicroseconds) != 0 {
			hour, min, sec := t.Clock()
			itoa(buf, hour, 2)
			*buf = append(*buf, ':')
			itoa(buf, min, 2)
			*buf = append(*buf, ':')
			itoa(buf, sec, 2)
			if l.flag&Lmicroseconds != 0 {
				*buf = append(*buf, '.')
				itoa(buf, t.Nanosecond()/1e3, 6)
			}
			*buf = append(*buf, ' ')
		}
	}
	if l.flag&(Lshortfile|Llongfile) != 0 {
		if l.flag&Lshortfile != 0 {
			short := file
			for i := len(file) - 1; i > 0; i-- {
				if file[i] == '/' {
					short = file[i+1:]
					break
				}
			}
			file = short
		}
		*buf = append(*buf, '<')
		*buf = append(*buf, file...)
		*buf = append(*buf, ':')
		itoa(buf, line, -1)
		*buf = append(*buf, `> `...)
	}
}

func printf(format string, v []interface{}) string {
	if len(format) == 0 {
		return fmt.Sprint(v...)
	} else {
		return fmt.Sprintf(format, v...)
	}
}

func (l *logger) Output(calldepth int, s string, optionLevel ...int) (err error) {
	level := LZero
	if len(optionLevel) != 0 {
		level = optionLevel[0]
	}
	if level == LZero || l.equal && level == l.level || !l.equal && level > l.level {
		level++
	} else {
		return
	}
	now := time.Now() // get this early.
	var file string
	var line int
	if len(s) != 0 {
		s = strconv.Quote(s)
	}
	if l.flag&(Lshortfile|Llongfile) != 0 {
		var ok bool
		_, file, line, ok = runtime.Caller(calldepth)
		if !ok {
			file = "???"
			line = 0
		}
	}
	l.mu.Lock()
	defer func() {
		l.mu.Unlock()
		if l.recover {
			_ = recover() // ignore panic
		}
	}()

	l.buf = l.buf[:0]

	l.formatHeader(&l.buf, now, file, line, level)

	l.buf = append(l.buf, s...)
	if len(s) > 0 && s[len(s)-1] != '\n' {
		l.buf = append(l.buf, '\n')
	}
	n := 0
	count := 0
	for len(l.buf) != 0 && (err == nil || err == io.ErrShortWrite) && count < 10 {
		n, err = l.out.Write(l.buf)
		l.buf = l.buf[n:]
		if err == io.ErrShortWrite {
			count++
		}
	}
	return
}

func (l *logger) ReadFrom(src io.Reader) (n int64, err error) {
	l.mu.Lock()
	defer func() {
		l.mu.Unlock()
		if l.recover {
			_ = recover() // ignore panic
		}
	}()
	return io.Copy(l.out, src)
}

func (l *logger) Write(p []byte) (n int, err error) {
	l.mu.Lock()
	defer func() {
		l.mu.Unlock()
		if l.recover {
			_ = recover() // ignore panic
		}
	}()
	wBytes := 0
	count := 0
	for len(p) != 0 && (err == nil || err == io.ErrShortWrite) && count < 10 {
		wBytes, err = l.out.Write(p)
		n += wBytes
		p = p[n:]
		if err == io.ErrShortWrite {
			count++
		}
	}
	return
}

func (l *logger) Print(v ...interface{}) {
	l.Output(2, printf("", v), LZero)
}

func (l *logger) Printf(format string, v ...interface{}) {
	l.Output(2, printf(format, v), LZero)
}

func (l *logger) Debug(v ...interface{}) {
	l.Output(2, printf("", v), LDebug)
}

func (l *logger) Debugf(format string, v ...interface{}) {
	l.Output(2, printf(format, v), LDebug)
}

func (l *logger) Info(v ...interface{}) {
	l.Output(2, printf("", v), LInfo)
}

func (l *logger) Infof(format string, v ...interface{}) {
	l.Output(2, printf(format, v), LInfo)
}

func (l *logger) Notify(v ...interface{}) {
	l.Output(2, printf("", v), LNotify)
}

func (l *logger) Notifyf(format string, v ...interface{}) {
	l.Output(2, printf(format, v), LNotify)
}

func (l *logger) Report(v ...interface{}) {
	l.Output(2, printf("", v), LReport)
}

func (l *logger) Reportf(format string, v ...interface{}) {
	l.Output(2, printf(format, v), LReport)
}

func (l *logger) Error(v ...interface{}) {
	l.Output(2, printf("", v), LError)
}

func (l *logger) Errorf(format string, v ...interface{}) {
	l.Output(2, printf(format, v), LError)
}

func (l *logger) Alert(v ...interface{}) {
	l.Output(2, printf("", v), LAlert)
}

func (l *logger) Alertf(format string, v ...interface{}) {
	l.Output(2, printf(format, v), LAlert)
}

func (l *logger) Panic(v ...interface{}) {
	l.Output(2, printf("", v), LPanic)
	if !l.dontPanic {
		panic(v)
	}
}

func (l *logger) Panicf(format string, v ...interface{}) {
	l.Output(2, printf(format, v), LPanic)
	if !l.dontPanic {
		panic(v)
	}
}

func (l *logger) Fatal(v ...interface{}) {
	l.Output(2, printf("", v), LFatal)
	if !l.dontExit {
		os.Exit(1)
	}
}

func (l *logger) Fatalf(format string, v ...interface{}) {
	l.Output(2, printf(format, v), LFatal)
	if !l.dontExit {
		os.Exit(1)
	}
}

func New(writer io.Writer, prefix string, flags ...int) Logger {
	ret := new(logger)
	ret.level = nr_levels
	hasflags := false
	for _, flag := range flags {
		switch flag {
		case MODE_EQUAL:
			ret.equal = true
		case MODE_NONE_NAME:
			ret.noneName = true
		case MODE_DONT_EXIT:
			ret.dontExit = true
		case MODE_DONT_PANIC:
			ret.dontPanic = true
		case MODE_RECOVER:
			ret.recover = true
		default:

			if flag >= 0 {
				hasflags = true
				ret.flag = ret.flag | flag
			} else {
				ret.level = flag
			}
		}
	}
	// defaults to LstdFlags.
	if !hasflags {
		ret.flag = LstdFlags
	}

	if ret.level <= nr_levels {
		ret.level = nr_levels + 1
	}

	ret.prefix = prefix
	ret.out = writer
	return ret
}
<|endoftext|>