<commit_before>package netlink

import (
	"fmt"
	"net"
	"strings"

	"github.com/vishvananda/netlink/nl"
	"github.com/vishvananda/netns"
	"golang.org/x/sys/unix"
)

// IFA_FLAGS is a u32 attribute.
const IFA_FLAGS = 0x8

// AddrAdd will add an IP address to a link device.
// Equivalent to: `ip addr add $addr dev $link`
func AddrAdd(link Link, addr *Addr) error {
	return pkgHandle.AddrAdd(link, addr)
}

// AddrAdd will add an IP address to a link device.
// Equivalent to: `ip addr add $addr dev $link`
func (h *Handle) AddrAdd(link Link, addr *Addr) error {
	req := h.newNetlinkRequest(unix.RTM_NEWADDR, unix.NLM_F_CREATE|unix.NLM_F_EXCL|unix.NLM_F_ACK)
	return h.addrHandle(link, addr, req)
}

// AddrReplace will replace (or, if not present, add) an IP address on a link device.
// Equivalent to: `ip addr replace $addr dev $link`
func AddrReplace(link Link, addr *Addr) error {
	return pkgHandle.AddrReplace(link, addr)
}

// AddrReplace will replace (or, if not present, add) an IP address on a link device.
// Equivalent to: `ip addr replace $addr dev $link`
func (h *Handle) AddrReplace(link Link, addr *Addr) error {
	req := h.newNetlinkRequest(unix.RTM_NEWADDR, unix.NLM_F_CREATE|unix.NLM_F_REPLACE|unix.NLM_F_ACK)
	return h.addrHandle(link, addr, req)
}

// AddrDel will delete an IP address from a link device.
// Equivalent to: `ip addr del $addr dev $link`
func AddrDel(link Link, addr *Addr) error {
	return pkgHandle.AddrDel(link, addr)
}

// AddrDel will delete an IP address from a link device.
// Equivalent to: `ip addr del $addr dev $link`
func (h *Handle) AddrDel(link Link, addr *Addr) error {
	req := h.newNetlinkRequest(unix.RTM_DELADDR, unix.NLM_F_ACK)
	return h.addrHandle(link, addr, req)
}

func (h *Handle) addrHandle(link Link, addr *Addr, req *nl.NetlinkRequest) error {
	base := link.Attrs()
	if addr.Label != "" && !strings.HasPrefix(addr.Label, base.Name) {
		return fmt.Errorf("label must begin with interface name")
	}
	h.ensureIndex(base)

	family := nl.GetIPFamily(addr.IP)

	msg := nl.NewIfAddrmsg(family)
	msg.Index = uint32(base.Index)
	msg.Scope = uint8(addr.Scope)
	prefixlen, masklen := addr.Mask.Size()
	msg.Prefixlen = uint8(prefixlen)
	req.AddData(msg)

	var localAddrData []byte
	if family == FAMILY_V4 {
		localAddrData = addr.IP.To4()
	} else {
		localAddrData = addr.IP.To16()
	}

	localData := nl.NewRtAttr(unix.IFA_LOCAL, localAddrData)
	req.AddData(localData)
	var peerAddrData []byte
	if addr.Peer != nil {
		if family == FAMILY_V4 {
			peerAddrData = addr.Peer.IP.To4()
		} else {
			peerAddrData = addr.Peer.IP.To16()
		}
	} else {
		peerAddrData = localAddrData
	}

	addressData := nl.NewRtAttr(unix.IFA_ADDRESS, peerAddrData)
	req.AddData(addressData)

	if addr.Flags != 0 {
		if addr.Flags <= 0xff {
			msg.IfAddrmsg.Flags = uint8(addr.Flags)
		} else {
			b := make([]byte, 4)
			native.PutUint32(b, uint32(addr.Flags))
			flagsData := nl.NewRtAttr(IFA_FLAGS, b)
			req.AddData(flagsData)
		}
	}

	if addr.Broadcast == nil {
		calcBroadcast := make(net.IP, masklen/8)
		for i := range localAddrData {
			calcBroadcast[i] = localAddrData[i] | ^addr.Mask[i]
		}
		addr.Broadcast = calcBroadcast
	}
	req.AddData(nl.NewRtAttr(unix.IFA_BROADCAST, addr.Broadcast))

	if addr.Label != "" {
		labelData := nl.NewRtAttr(unix.IFA_LABEL, nl.ZeroTerminated(addr.Label))
		req.AddData(labelData)
	}

	_, err := req.Execute(unix.NETLINK_ROUTE, 0)
	return err
}

// AddrList gets a list of IP addresses in the system.
// Equivalent to: `ip addr show`.
// The list can be filtered by link and ip family.
func AddrList(link Link, family int) ([]Addr, error) {
	return pkgHandle.AddrList(link, family)
}

// AddrList gets a list of IP addresses in the system.
// Equivalent to: `ip addr show`.
// The list can be filtered by link and ip family.
func (h *Handle) AddrList(link Link, family int) ([]Addr, error) {
	req := h.newNetlinkRequest(unix.RTM_GETADDR, unix.NLM_F_DUMP)
	msg := nl.NewIfInfomsg(family)
	req.AddData(msg)

	msgs, err := req.Execute(unix.NETLINK_ROUTE, unix.RTM_NEWADDR)
	if err != nil {
		return nil, err
	}

	indexFilter := 0
	if link != nil {
		base := link.Attrs()
		h.ensureIndex(base)
		indexFilter = base.Index
	}

	var res []Addr
	for _, m := range msgs {
		addr, msgFamily, ifindex, err := parseAddr(m)
		if err != nil {
			return res, err
		}

		if link != nil && ifindex != indexFilter {
			// Ignore messages from other interfaces
			continue
		}

		if family != FAMILY_ALL && msgFamily != family {
			continue
		}

		res = append(res, addr)
	}

	return res, nil
}

func parseAddr(m []byte) (addr Addr, family, index int, err error) {
	msg := nl.DeserializeIfAddrmsg(m)

	family = -1
	index = -1

	attrs, err1 := nl.ParseRouteAttr(m[msg.Len():])
	if err1 != nil {
		err = err1
		return
	}

	family = int(msg.Family)
	index = int(msg.Index)

	var local, dst *net.IPNet
	for _, attr := range attrs {
		switch attr.Attr.Type {
		case unix.IFA_ADDRESS:
			dst = &net.IPNet{
				IP:   attr.Value,
				Mask: net.CIDRMask(int(msg.Prefixlen), 8*len(attr.Value)),
			}
			addr.Peer = dst
		case unix.IFA_LOCAL:
			local = &net.IPNet{
				IP:   attr.Value,
				Mask: net.CIDRMask(int(msg.Prefixlen), 8*len(attr.Value)),
			}
			addr.IPNet = local
		case unix.IFA_BROADCAST:
			addr.Broadcast = attr.Value
		case unix.IFA_LABEL:
			addr.Label = string(attr.Value[:len(attr.Value)-1])
		case IFA_FLAGS:
			addr.Flags = int(native.Uint32(attr.Value[0:4]))
		case nl.IFA_CACHEINFO:
			ci := nl.DeserializeIfaCacheInfo(attr.Value)
			addr.PreferedLft = int(ci.IfaPrefered)
			addr.ValidLft = int(ci.IfaValid)
		}
	}

	// IFA_LOCAL should be there but if not, fall back to IFA_ADDRESS
	if local != nil {
		addr.IPNet = local
	} else {
		addr.IPNet = dst
	}
	addr.Scope = int(msg.Scope)

	return
}

type AddrUpdate struct {
	LinkAddress net.IPNet
	LinkIndex   int
	Flags       int
	Scope       int
	PreferedLft int
	ValidLft    int
	NewAddr     bool // true=added false=deleted
}

// AddrSubscribe takes a chan down which notifications will be sent
// when addresses change.  Close the 'done' chan to stop subscription.
func AddrSubscribe(ch chan<- AddrUpdate, done <-chan struct{}) error {
	return addrSubscribeAt(netns.None(), netns.None(), ch, done, nil)
}

// AddrSubscribeAt works like AddrSubscribe plus it allows the caller
// to choose the network namespace in which to subscribe (ns).
func AddrSubscribeAt(ns netns.NsHandle, ch chan<- AddrUpdate, done <-chan struct{}) error {
	return addrSubscribeAt(ns, netns.None(), ch, done, nil)
}

// AddrSubscribeOptions contains a set of options to use with
// AddrSubscribeWithOptions.
type AddrSubscribeOptions struct {
	Namespace     *netns.NsHandle
	ErrorCallback func(error)
}

// AddrSubscribeWithOptions work like AddrSubscribe but enable to
// provide additional options to modify the behavior. Currently, the
// namespace can be provided as well as an error callback.
func AddrSubscribeWithOptions(ch chan<- AddrUpdate, done <-chan struct{}, options AddrSubscribeOptions) error {
	if options.Namespace == nil {
		none := netns.None()
		options.Namespace = &none
	}
	return addrSubscribeAt(*options.Namespace, netns.None(), ch, done, options.ErrorCallback)
}

func addrSubscribeAt(newNs, curNs netns.NsHandle, ch chan<- AddrUpdate, done <-chan struct{}, cberr func(error)) error {
	s, err := nl.SubscribeAt(newNs, curNs, unix.NETLINK_ROUTE, unix.RTNLGRP_IPV4_IFADDR, unix.RTNLGRP_IPV6_IFADDR)
	if err != nil {
		return err
	}
	if done != nil {
		go func() {
			<-done
			s.Close()
		}()
	}
	go func() {
		defer close(ch)
		for {
			msgs, err := s.Receive()
			if err != nil {
				if cberr != nil {
					cberr(err)
				}
				return
			}
			for _, m := range msgs {
				msgType := m.Header.Type
				if msgType != unix.RTM_NEWADDR && msgType != unix.RTM_DELADDR {
					if cberr != nil {
						cberr(fmt.Errorf("bad message type: %d", msgType))
					}
					return
				}

				addr, _, ifindex, err := parseAddr(m.Data)
				if err != nil {
					if cberr != nil {
						cberr(fmt.Errorf("could not parse address: %v", err))
					}
					return
				}

				ch <- AddrUpdate{LinkAddress: *addr.IPNet,
					LinkIndex:   ifindex,
					NewAddr:     msgType == unix.RTM_NEWADDR,
					Flags:       addr.Flags,
					Scope:       addr.Scope,
					PreferedLft: addr.PreferedLft,
					ValidLft:    addr.ValidLft}
			}
		}
	}()

	return nil
}
<commit_msg>addr_linux: Skip BROADCAST and LABEL for non-ipv4<commit_after>package netlink

import (
	"fmt"
	"net"
	"strings"

	"github.com/vishvananda/netlink/nl"
	"github.com/vishvananda/netns"
	"golang.org/x/sys/unix"
)

// IFA_FLAGS is a u32 attribute.
const IFA_FLAGS = 0x8

// AddrAdd will add an IP address to a link device.
// Equivalent to: `ip addr add $addr dev $link`
func AddrAdd(link Link, addr *Addr) error {
	return pkgHandle.AddrAdd(link, addr)
}

// AddrAdd will add an IP address to a link device.
// Equivalent to: `ip addr add $addr dev $link`
func (h *Handle) AddrAdd(link Link, addr *Addr) error {
	req := h.newNetlinkRequest(unix.RTM_NEWADDR, unix.NLM_F_CREATE|unix.NLM_F_EXCL|unix.NLM_F_ACK)
	return h.addrHandle(link, addr, req)
}

// AddrReplace will replace (or, if not present, add) an IP address on a link device.
// Equivalent to: `ip addr replace $addr dev $link`
func AddrReplace(link Link, addr *Addr) error {
	return pkgHandle.AddrReplace(link, addr)
}

// AddrReplace will replace (or, if not present, add) an IP address on a link device.
// Equivalent to: `ip addr replace $addr dev $link`
func (h *Handle) AddrReplace(link Link, addr *Addr) error {
	req := h.newNetlinkRequest(unix.RTM_NEWADDR, unix.NLM_F_CREATE|unix.NLM_F_REPLACE|unix.NLM_F_ACK)
	return h.addrHandle(link, addr, req)
}

// AddrDel will delete an IP address from a link device.
// Equivalent to: `ip addr del $addr dev $link`
func AddrDel(link Link, addr *Addr) error {
	return pkgHandle.AddrDel(link, addr)
}

// AddrDel will delete an IP address from a link device.
// Equivalent to: `ip addr del $addr dev $link`
func (h *Handle) AddrDel(link Link, addr *Addr) error {
	req := h.newNetlinkRequest(unix.RTM_DELADDR, unix.NLM_F_ACK)
	return h.addrHandle(link, addr, req)
}

func (h *Handle) addrHandle(link Link, addr *Addr, req *nl.NetlinkRequest) error {
	base := link.Attrs()
	if addr.Label != "" && !strings.HasPrefix(addr.Label, base.Name) {
		return fmt.Errorf("label must begin with interface name")
	}
	h.ensureIndex(base)

	family := nl.GetIPFamily(addr.IP)

	msg := nl.NewIfAddrmsg(family)
	msg.Index = uint32(base.Index)
	msg.Scope = uint8(addr.Scope)
	prefixlen, masklen := addr.Mask.Size()
	msg.Prefixlen = uint8(prefixlen)
	req.AddData(msg)

	var localAddrData []byte
	if family == FAMILY_V4 {
		localAddrData = addr.IP.To4()
	} else {
		localAddrData = addr.IP.To16()
	}

	localData := nl.NewRtAttr(unix.IFA_LOCAL, localAddrData)
	req.AddData(localData)
	var peerAddrData []byte
	if addr.Peer != nil {
		if family == FAMILY_V4 {
			peerAddrData = addr.Peer.IP.To4()
		} else {
			peerAddrData = addr.Peer.IP.To16()
		}
	} else {
		peerAddrData = localAddrData
	}

	addressData := nl.NewRtAttr(unix.IFA_ADDRESS, peerAddrData)
	req.AddData(addressData)

	if addr.Flags != 0 {
		if addr.Flags <= 0xff {
			msg.IfAddrmsg.Flags = uint8(addr.Flags)
		} else {
			b := make([]byte, 4)
			native.PutUint32(b, uint32(addr.Flags))
			flagsData := nl.NewRtAttr(IFA_FLAGS, b)
			req.AddData(flagsData)
		}
	}

	if family == FAMILY_V4 {
		if addr.Broadcast == nil {
			calcBroadcast := make(net.IP, masklen/8)
			for i := range localAddrData {
				calcBroadcast[i] = localAddrData[i] | ^addr.Mask[i]
			}
			addr.Broadcast = calcBroadcast
		}
		req.AddData(nl.NewRtAttr(unix.IFA_BROADCAST, addr.Broadcast))

		if addr.Label != "" {
			labelData := nl.NewRtAttr(unix.IFA_LABEL, nl.ZeroTerminated(addr.Label))
			req.AddData(labelData)
		}
	}

	_, err := req.Execute(unix.NETLINK_ROUTE, 0)
	return err
}

// AddrList gets a list of IP addresses in the system.
// Equivalent to: `ip addr show`.
// The list can be filtered by link and ip family.
func AddrList(link Link, family int) ([]Addr, error) {
	return pkgHandle.AddrList(link, family)
}

// AddrList gets a list of IP addresses in the system.
// Equivalent to: `ip addr show`.
// The list can be filtered by link and ip family.
func (h *Handle) AddrList(link Link, family int) ([]Addr, error) {
	req := h.newNetlinkRequest(unix.RTM_GETADDR, unix.NLM_F_DUMP)
	msg := nl.NewIfInfomsg(family)
	req.AddData(msg)

	msgs, err := req.Execute(unix.NETLINK_ROUTE, unix.RTM_NEWADDR)
	if err != nil {
		return nil, err
	}

	indexFilter := 0
	if link != nil {
		base := link.Attrs()
		h.ensureIndex(base)
		indexFilter = base.Index
	}

	var res []Addr
	for _, m := range msgs {
		addr, msgFamily, ifindex, err := parseAddr(m)
		if err != nil {
			return res, err
		}

		if link != nil && ifindex != indexFilter {
			// Ignore messages from other interfaces
			continue
		}

		if family != FAMILY_ALL && msgFamily != family {
			continue
		}

		res = append(res, addr)
	}

	return res, nil
}

func parseAddr(m []byte) (addr Addr, family, index int, err error) {
	msg := nl.DeserializeIfAddrmsg(m)

	family = -1
	index = -1

	attrs, err1 := nl.ParseRouteAttr(m[msg.Len():])
	if err1 != nil {
		err = err1
		return
	}

	family = int(msg.Family)
	index = int(msg.Index)

	var local, dst *net.IPNet
	for _, attr := range attrs {
		switch attr.Attr.Type {
		case unix.IFA_ADDRESS:
			dst = &net.IPNet{
				IP:   attr.Value,
				Mask: net.CIDRMask(int(msg.Prefixlen), 8*len(attr.Value)),
			}
			addr.Peer = dst
		case unix.IFA_LOCAL:
			local = &net.IPNet{
				IP:   attr.Value,
				Mask: net.CIDRMask(int(msg.Prefixlen), 8*len(attr.Value)),
			}
			addr.IPNet = local
		case unix.IFA_BROADCAST:
			addr.Broadcast = attr.Value
		case unix.IFA_LABEL:
			addr.Label = string(attr.Value[:len(attr.Value)-1])
		case IFA_FLAGS:
			addr.Flags = int(native.Uint32(attr.Value[0:4]))
		case nl.IFA_CACHEINFO:
			ci := nl.DeserializeIfaCacheInfo(attr.Value)
			addr.PreferedLft = int(ci.IfaPrefered)
			addr.ValidLft = int(ci.IfaValid)
		}
	}

	// IFA_LOCAL should be there but if not, fall back to IFA_ADDRESS
	if local != nil {
		addr.IPNet = local
	} else {
		addr.IPNet = dst
	}
	addr.Scope = int(msg.Scope)

	return
}

type AddrUpdate struct {
	LinkAddress net.IPNet
	LinkIndex   int
	Flags       int
	Scope       int
	PreferedLft int
	ValidLft    int
	NewAddr     bool // true=added false=deleted
}

// AddrSubscribe takes a chan down which notifications will be sent
// when addresses change.  Close the 'done' chan to stop subscription.
func AddrSubscribe(ch chan<- AddrUpdate, done <-chan struct{}) error {
	return addrSubscribeAt(netns.None(), netns.None(), ch, done, nil)
}

// AddrSubscribeAt works like AddrSubscribe plus it allows the caller
// to choose the network namespace in which to subscribe (ns).
func AddrSubscribeAt(ns netns.NsHandle, ch chan<- AddrUpdate, done <-chan struct{}) error {
	return addrSubscribeAt(ns, netns.None(), ch, done, nil)
}

// AddrSubscribeOptions contains a set of options to use with
// AddrSubscribeWithOptions.
type AddrSubscribeOptions struct {
	Namespace     *netns.NsHandle
	ErrorCallback func(error)
}

// AddrSubscribeWithOptions work like AddrSubscribe but enable to
// provide additional options to modify the behavior. Currently, the
// namespace can be provided as well as an error callback.
func AddrSubscribeWithOptions(ch chan<- AddrUpdate, done <-chan struct{}, options AddrSubscribeOptions) error {
	if options.Namespace == nil {
		none := netns.None()
		options.Namespace = &none
	}
	return addrSubscribeAt(*options.Namespace, netns.None(), ch, done, options.ErrorCallback)
}

func addrSubscribeAt(newNs, curNs netns.NsHandle, ch chan<- AddrUpdate, done <-chan struct{}, cberr func(error)) error {
	s, err := nl.SubscribeAt(newNs, curNs, unix.NETLINK_ROUTE, unix.RTNLGRP_IPV4_IFADDR, unix.RTNLGRP_IPV6_IFADDR)
	if err != nil {
		return err
	}
	if done != nil {
		go func() {
			<-done
			s.Close()
		}()
	}
	go func() {
		defer close(ch)
		for {
			msgs, err := s.Receive()
			if err != nil {
				if cberr != nil {
					cberr(err)
				}
				return
			}
			for _, m := range msgs {
				msgType := m.Header.Type
				if msgType != unix.RTM_NEWADDR && msgType != unix.RTM_DELADDR {
					if cberr != nil {
						cberr(fmt.Errorf("bad message type: %d", msgType))
					}
					return
				}

				addr, _, ifindex, err := parseAddr(m.Data)
				if err != nil {
					if cberr != nil {
						cberr(fmt.Errorf("could not parse address: %v", err))
					}
					return
				}

				ch <- AddrUpdate{LinkAddress: *addr.IPNet,
					LinkIndex:   ifindex,
					NewAddr:     msgType == unix.RTM_NEWADDR,
					Flags:       addr.Flags,
					Scope:       addr.Scope,
					PreferedLft: addr.PreferedLft,
					ValidLft:    addr.ValidLft}
			}
		}
	}()

	return nil
}
<|endoftext|>


<commit_before>package order

import (
	"encoding/xml"
	"errors"
	"strconv"
	"strings"
	"time"

	"github.com/silenceper/wechat/v2/pay/config"
	"github.com/silenceper/wechat/v2/util"
)

// https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1
var payGateway = "https://api.mch.weixin.qq.com/pay/unifiedorder"

// SUCCESS 表示支付成功
const SUCCESS = "SUCCESS"

// Order struct extends context
type Order struct {
	*config.Config
}

// NewOrder return an instance of order package
func NewOrder(cfg *config.Config) *Order {
	order := Order{cfg}
	return &order
}

// Params was NEEDED when request Unified order
// 传入的参数，用于生成 prepay_id 的必需参数
type Params struct {
	TotalFee   string
	CreateIP   string
	Body       string
	OutTradeNo string
	TimeExpire string // 订单失效时间，格式为yyyyMMddHHmmss，如2009年12月27日9点10分10秒表示为20091227091010。
	OpenID     string
	TradeType  string
	SignType   string
	Detail     string
	Attach     string
	GoodsTag   string
	NotifyURL  string
}

// Config 是传出用于 js sdk 用的参数
type Config struct {
	Timestamp string `json:"timestamp"`
	NonceStr  string `json:"nonceStr"`
	PrePayID  string `json:"prePayId"`
	SignType  string `json:"signType"`
	Package   string `json:"package"`
	PaySign   string `json:"paySign"`
}

// ConfigForApp 是传出用于 app sdk 用的参数
type ConfigForApp struct {
	AppID     string `json:"appid"`
	MchID     string `json:"partnerid"` // 微信支付分配的商户号
	PrePayID  string `json:"prepayid"`
	Package   string `json:"package"`
	NonceStr  string `json:"nonceStr"`
	Timestamp string `json:"timestamp"`
	Sign      string `json:"sign"`
}

// PreOrder 是 Unified order 接口的返回
type PreOrder struct {
	ReturnCode string `xml:"return_code"`
	ReturnMsg  string `xml:"return_msg"`
	AppID      string `xml:"appid,omitempty"`
	MchID      string `xml:"mch_id,omitempty"`
	NonceStr   string `xml:"nonce_str,omitempty"`
	Sign       string `xml:"sign,omitempty"`
	ResultCode string `xml:"result_code,omitempty"`
	TradeType  string `xml:"trade_type,omitempty"`
	PrePayID   string `xml:"prepay_id,omitempty"`
	CodeURL    string `xml:"code_url,omitempty"`
	MWebURL    string `xml:"mweb_url,omitempty"`
	ErrCode    string `xml:"err_code,omitempty"`
	ErrCodeDes string `xml:"err_code_des,omitempty"`
}

// payRequest 接口请求参数
type payRequest struct {
	AppID          string `xml:"appid"`                 // 公众账号ID
	MchID          string `xml:"mch_id"`                // 商户号
	DeviceInfo     string `xml:"device_info,omitempty"` // 设备号
	NonceStr       string `xml:"nonce_str"`             // 随机字符串
	Sign           string `xml:"sign"`                  // 签名
	SignType       string `xml:"sign_type,omitempty"`   // 签名类型
	Body           string `xml:"body"`                  // 商品描述
	Detail         string `xml:"detail,omitempty"`      // 商品详情
	Attach         string `xml:"attach,omitempty"`      // 附加数据
	OutTradeNo     string `xml:"out_trade_no"`          // 商户订单号
	FeeType        string `xml:"fee_type,omitempty"`    // 标价币种
	TotalFee       string `xml:"total_fee"`             // 标价金额
	SpbillCreateIP string `xml:"spbill_create_ip"`      // 终端IP
	TimeStart      string `xml:"time_start,omitempty"`  // 交易起始时间
	TimeExpire     string `xml:"time_expire,omitempty"` // 交易结束时间
	GoodsTag       string `xml:"goods_tag,omitempty"`   // 订单优惠标记
	NotifyURL      string `xml:"notify_url"`            // 通知地址
	TradeType      string `xml:"trade_type"`            // 交易类型
	ProductID      string `xml:"product_id,omitempty"`  // 商品ID
	LimitPay       string `xml:"limit_pay,omitempty"`   // 指定支付方式
	OpenID         string `xml:"openid,omitempty"`      // 用户标识
	SceneInfo      string `xml:"scene_info,omitempty"`  // 场景信息

	XMLName struct{} `xml:"xml"`
}

func (req *payRequest) BridgePayRequest(p *Params, AppID, MchID, nonceStr, sign string) *payRequest {
	request := payRequest{
		AppID:          AppID,
		MchID:          MchID,
		NonceStr:       nonceStr,
		Sign:           sign,
		Body:           p.Body,
		OutTradeNo:     p.OutTradeNo,
		TotalFee:       p.TotalFee,
		SpbillCreateIP: p.CreateIP,
		NotifyURL:      p.NotifyURL,
		TradeType:      p.TradeType,
		OpenID:         p.OpenID,
		SignType:       p.SignType,
		Detail:         p.Detail,
		Attach:         p.Attach,
		GoodsTag:       p.GoodsTag,
	}
	return &request
}

// BridgeConfig get js bridge config
func (o *Order) BridgeConfig(p *Params) (cfg Config, err error) {
	var (
		buffer    strings.Builder
		timestamp = strconv.FormatInt(time.Now().Unix(), 10)
	)
	order, err := o.PrePayOrder(p)
	if err != nil {
		return
	}
	buffer.WriteString("appId=")
	buffer.WriteString(order.AppID)
	buffer.WriteString("&nonceStr=")
	buffer.WriteString(order.NonceStr)
	buffer.WriteString("&package=")
	buffer.WriteString("prepay_id=" + order.PrePayID)
	buffer.WriteString("&signType=")
	buffer.WriteString(p.SignType)
	buffer.WriteString("&timeStamp=")
	buffer.WriteString(timestamp)
	buffer.WriteString("&key=")
	buffer.WriteString(o.Key)

	sign, err := util.CalculateSign(buffer.String(), p.SignType, o.Key)
	if err != nil {
		return
	}
	// 签名
	cfg.PaySign = sign
	cfg.NonceStr = order.NonceStr
	cfg.Timestamp = timestamp
	cfg.PrePayID = order.PrePayID
	cfg.SignType = p.SignType
	cfg.Package = "prepay_id=" + order.PrePayID
	return
}

// BridgeAppConfig get app bridge config
func (o *Order) BridgeAppConfig(p *Params) (cfg ConfigForApp, err error) {
	var (
		timestamp = strconv.FormatInt(time.Now().Unix(), 10)
		noncestr  = util.RandomStr(32)
		_package  = "Sign=WXPay"
	)
	order, err := o.PrePayOrder(p)
	if err != nil {
		return
	}

	result := map[string]string{
		"appid":     order.AppID,
		"partnerid": order.MchID,
		"prepayid":  order.PrePayID,
		"package":   _package,
		"noncestr":  noncestr,
		"timestamp": timestamp,
	}
	// 签名
	sign, err := util.ParamSign(result, o.Key)
	if err != nil {
		return
	}
	result["sign"] = sign
	cfg = ConfigForApp{
		AppID:     result["appid"],
		MchID:     result["partnerid"],
		PrePayID:  result["prepayid"],
		Package:   result["package"],
		NonceStr:  result["noncestr"],
		Timestamp: result["timestamp"],
		Sign:      result["sign"],
	}
	return
}

// PrePayOrder return data for invoke wechat payment
func (o *Order) PrePayOrder(p *Params) (payOrder PreOrder, err error) {
	nonceStr := util.RandomStr(32)

	// 通知地址
	if len(p.NotifyURL) == 0 {
		p.NotifyURL = o.NotifyURL // 默认使用order.NotifyURL
	}

	param := map[string]string{
		"appid":            o.AppID,
		"body":             p.Body,
		"mch_id":           o.MchID,
		"nonce_str":        nonceStr,
		"out_trade_no":     p.OutTradeNo,
		"spbill_create_ip": p.CreateIP,
		"total_fee":        p.TotalFee,
		"trade_type":       p.TradeType,
		"openid":           p.OpenID,
		"sign_type":        p.SignType,
		"detail":           p.Detail,
		"attach":           p.Attach,
		"goods_tag":        p.GoodsTag,
		"notify_url":       p.NotifyURL,
	}
	// 签名类型
	if param["sign_type"] == "" {
		param["sign_type"] = util.SignTypeMD5
	}

	if p.TimeExpire != "" {
		// 如果有传入交易结束时间
		param["time_expire"] = p.TimeExpire
	}

	sign, err := util.ParamSign(param, o.Key)
	if err != nil {
		return
	}
	request := new(payRequest).BridgePayRequest(p, o.AppID, o.MchID, nonceStr, sign)
	if len(p.TimeExpire) > 0 {
		// 如果有传入交易结束时间
		request.TimeExpire = p.TimeExpire
	}
	rawRet, err := util.PostXML(payGateway, request)
	if err != nil {
		return
	}
	err = xml.Unmarshal(rawRet, &payOrder)
	if err != nil {
		return
	}
	if payOrder.ReturnCode == SUCCESS {
		// pay success
		if payOrder.ResultCode == SUCCESS {
			err = nil
			return
		}
		err = errors.New(payOrder.ErrCode + payOrder.ErrCodeDes)
		return
	}
	err = errors.New("[msg : xmlUnmarshalError] [rawReturn : " + string(rawRet) + "] [sign : " + sign + "]")
	return
}

// PrePayID will request wechat merchant api and request for a pre payment order id
func (o *Order) PrePayID(p *Params) (prePayID string, err error) {
	order, err := o.PrePayOrder(p)
	if err != nil {
		return
	}
	if order.PrePayID == "" {
		err = errors.New("empty prepayid")
	}
	prePayID = order.PrePayID
	return
}
<commit_msg>修复不传sign_type导致request sign_type无默认值的bug (#480)<commit_after>package order

import (
	"encoding/xml"
	"errors"
	"strconv"
	"strings"
	"time"

	"github.com/silenceper/wechat/v2/pay/config"
	"github.com/silenceper/wechat/v2/util"
)

// https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1
var payGateway = "https://api.mch.weixin.qq.com/pay/unifiedorder"

// SUCCESS 表示支付成功
const SUCCESS = "SUCCESS"

// Order struct extends context
type Order struct {
	*config.Config
}

// NewOrder return an instance of order package
func NewOrder(cfg *config.Config) *Order {
	order := Order{cfg}
	return &order
}

// Params was NEEDED when request Unified order
// 传入的参数，用于生成 prepay_id 的必需参数
type Params struct {
	TotalFee   string
	CreateIP   string
	Body       string
	OutTradeNo string
	TimeExpire string // 订单失效时间，格式为yyyyMMddHHmmss，如2009年12月27日9点10分10秒表示为20091227091010。
	OpenID     string
	TradeType  string
	SignType   string
	Detail     string
	Attach     string
	GoodsTag   string
	NotifyURL  string
}

// Config 是传出用于 js sdk 用的参数
type Config struct {
	Timestamp string `json:"timestamp"`
	NonceStr  string `json:"nonceStr"`
	PrePayID  string `json:"prePayId"`
	SignType  string `json:"signType"`
	Package   string `json:"package"`
	PaySign   string `json:"paySign"`
}

// ConfigForApp 是传出用于 app sdk 用的参数
type ConfigForApp struct {
	AppID     string `json:"appid"`
	MchID     string `json:"partnerid"` // 微信支付分配的商户号
	PrePayID  string `json:"prepayid"`
	Package   string `json:"package"`
	NonceStr  string `json:"nonceStr"`
	Timestamp string `json:"timestamp"`
	Sign      string `json:"sign"`
}

// PreOrder 是 Unified order 接口的返回
type PreOrder struct {
	ReturnCode string `xml:"return_code"`
	ReturnMsg  string `xml:"return_msg"`
	AppID      string `xml:"appid,omitempty"`
	MchID      string `xml:"mch_id,omitempty"`
	NonceStr   string `xml:"nonce_str,omitempty"`
	Sign       string `xml:"sign,omitempty"`
	ResultCode string `xml:"result_code,omitempty"`
	TradeType  string `xml:"trade_type,omitempty"`
	PrePayID   string `xml:"prepay_id,omitempty"`
	CodeURL    string `xml:"code_url,omitempty"`
	MWebURL    string `xml:"mweb_url,omitempty"`
	ErrCode    string `xml:"err_code,omitempty"`
	ErrCodeDes string `xml:"err_code_des,omitempty"`
}

// payRequest 接口请求参数
type payRequest struct {
	AppID          string `xml:"appid"`                 // 公众账号ID
	MchID          string `xml:"mch_id"`                // 商户号
	DeviceInfo     string `xml:"device_info,omitempty"` // 设备号
	NonceStr       string `xml:"nonce_str"`             // 随机字符串
	Sign           string `xml:"sign"`                  // 签名
	SignType       string `xml:"sign_type,omitempty"`   // 签名类型
	Body           string `xml:"body"`                  // 商品描述
	Detail         string `xml:"detail,omitempty"`      // 商品详情
	Attach         string `xml:"attach,omitempty"`      // 附加数据
	OutTradeNo     string `xml:"out_trade_no"`          // 商户订单号
	FeeType        string `xml:"fee_type,omitempty"`    // 标价币种
	TotalFee       string `xml:"total_fee"`             // 标价金额
	SpbillCreateIP string `xml:"spbill_create_ip"`      // 终端IP
	TimeStart      string `xml:"time_start,omitempty"`  // 交易起始时间
	TimeExpire     string `xml:"time_expire,omitempty"` // 交易结束时间
	GoodsTag       string `xml:"goods_tag,omitempty"`   // 订单优惠标记
	NotifyURL      string `xml:"notify_url"`            // 通知地址
	TradeType      string `xml:"trade_type"`            // 交易类型
	ProductID      string `xml:"product_id,omitempty"`  // 商品ID
	LimitPay       string `xml:"limit_pay,omitempty"`   // 指定支付方式
	OpenID         string `xml:"openid,omitempty"`      // 用户标识
	SceneInfo      string `xml:"scene_info,omitempty"`  // 场景信息

	XMLName struct{} `xml:"xml"`
}

func (req *payRequest) BridgePayRequest(p *Params, AppID, MchID, nonceStr, sign string) *payRequest {
	request := payRequest{
		AppID:          AppID,
		MchID:          MchID,
		NonceStr:       nonceStr,
		Sign:           sign,
		Body:           p.Body,
		OutTradeNo:     p.OutTradeNo,
		TotalFee:       p.TotalFee,
		SpbillCreateIP: p.CreateIP,
		NotifyURL:      p.NotifyURL,
		TradeType:      p.TradeType,
		OpenID:         p.OpenID,
		SignType:       p.SignType,
		Detail:         p.Detail,
		Attach:         p.Attach,
		GoodsTag:       p.GoodsTag,
	}
	return &request
}

// BridgeConfig get js bridge config
func (o *Order) BridgeConfig(p *Params) (cfg Config, err error) {
	var (
		buffer    strings.Builder
		timestamp = strconv.FormatInt(time.Now().Unix(), 10)
	)
	order, err := o.PrePayOrder(p)
	if err != nil {
		return
	}
	buffer.WriteString("appId=")
	buffer.WriteString(order.AppID)
	buffer.WriteString("&nonceStr=")
	buffer.WriteString(order.NonceStr)
	buffer.WriteString("&package=")
	buffer.WriteString("prepay_id=" + order.PrePayID)
	buffer.WriteString("&signType=")
	buffer.WriteString(p.SignType)
	buffer.WriteString("&timeStamp=")
	buffer.WriteString(timestamp)
	buffer.WriteString("&key=")
	buffer.WriteString(o.Key)

	sign, err := util.CalculateSign(buffer.String(), p.SignType, o.Key)
	if err != nil {
		return
	}
	// 签名
	cfg.PaySign = sign
	cfg.NonceStr = order.NonceStr
	cfg.Timestamp = timestamp
	cfg.PrePayID = order.PrePayID
	cfg.SignType = p.SignType
	cfg.Package = "prepay_id=" + order.PrePayID
	return
}

// BridgeAppConfig get app bridge config
func (o *Order) BridgeAppConfig(p *Params) (cfg ConfigForApp, err error) {
	var (
		timestamp = strconv.FormatInt(time.Now().Unix(), 10)
		noncestr  = util.RandomStr(32)
		_package  = "Sign=WXPay"
	)
	order, err := o.PrePayOrder(p)
	if err != nil {
		return
	}

	result := map[string]string{
		"appid":     order.AppID,
		"partnerid": order.MchID,
		"prepayid":  order.PrePayID,
		"package":   _package,
		"noncestr":  noncestr,
		"timestamp": timestamp,
	}
	// 签名
	sign, err := util.ParamSign(result, o.Key)
	if err != nil {
		return
	}
	result["sign"] = sign
	cfg = ConfigForApp{
		AppID:     result["appid"],
		MchID:     result["partnerid"],
		PrePayID:  result["prepayid"],
		Package:   result["package"],
		NonceStr:  result["noncestr"],
		Timestamp: result["timestamp"],
		Sign:      result["sign"],
	}
	return
}

// PrePayOrder return data for invoke wechat payment
func (o *Order) PrePayOrder(p *Params) (payOrder PreOrder, err error) {
	nonceStr := util.RandomStr(32)

	// 通知地址
	if len(p.NotifyURL) == 0 {
		p.NotifyURL = o.NotifyURL // 默认使用order.NotifyURL
	}

	// 签名类型
	if p.SignType == "" {
		p.SignType = util.SignTypeMD5
	}

	param := map[string]string{
		"appid":            o.AppID,
		"body":             p.Body,
		"mch_id":           o.MchID,
		"nonce_str":        nonceStr,
		"out_trade_no":     p.OutTradeNo,
		"spbill_create_ip": p.CreateIP,
		"total_fee":        p.TotalFee,
		"trade_type":       p.TradeType,
		"openid":           p.OpenID,
		"sign_type":        p.SignType,
		"detail":           p.Detail,
		"attach":           p.Attach,
		"goods_tag":        p.GoodsTag,
		"notify_url":       p.NotifyURL,
	}

	if p.TimeExpire != "" {
		// 如果有传入交易结束时间
		param["time_expire"] = p.TimeExpire
	}

	sign, err := util.ParamSign(param, o.Key)
	if err != nil {
		return
	}
	request := new(payRequest).BridgePayRequest(p, o.AppID, o.MchID, nonceStr, sign)
	if len(p.TimeExpire) > 0 {
		// 如果有传入交易结束时间
		request.TimeExpire = p.TimeExpire
	}
	rawRet, err := util.PostXML(payGateway, request)
	if err != nil {
		return
	}
	err = xml.Unmarshal(rawRet, &payOrder)
	if err != nil {
		return
	}
	if payOrder.ReturnCode == SUCCESS {
		// pay success
		if payOrder.ResultCode == SUCCESS {
			err = nil
			return
		}
		err = errors.New(payOrder.ErrCode + payOrder.ErrCodeDes)
		return
	}
	err = errors.New("[msg : xmlUnmarshalError] [rawReturn : " + string(rawRet) + "] [sign : " + sign + "]")
	return
}

// PrePayID will request wechat merchant api and request for a pre payment order id
func (o *Order) PrePayID(p *Params) (prePayID string, err error) {
	order, err := o.PrePayOrder(p)
	if err != nil {
		return
	}
	if order.PrePayID == "" {
		err = errors.New("empty prepayid")
	}
	prePayID = order.PrePayID
	return
}
<|endoftext|>


<commit_before>/*
	Simple logger package for go services supervised by DJB deamontools
*/

package logger

import (
	"fmt"
	"strings"
	"time"
)

const (
	CRITICAL = 4
	ERROR    = 3
	WARNING  = 2
	NOTICE   = 1
	DEBUG    = 0
)

type Logger struct {
	level        int
	addTimestamp bool
}

var levels = map[string]int{
	"critical": CRITICAL,
	"error":    ERROR,
	"warning":  WARNING,
	"notice":   NOTICE,
	"debug":    DEBUG,
}

var rlevels = [5]string{
	"debug",
	"notice",
	"warning",
	"error",
	"critical",
}

func init() {

}

func (l *Logger) SetLevel(level string) {
	level = strings.ToLower(level)
	l.level = levels[level]
}

func (l *Logger) SetTimeStamp(ts bool) {
	l.addTimestamp = ts
}

// Base logger
func (l *Logger) log(level int, v ...interface{}) {
	if level >= l.level {
		if l.addTimestamp {
			fmt.Print(fmt.Sprintf("%d - ", time.Now().UnixNano()))
		}
		fmt.Print(fmt.Sprintf("%s - ", strings.ToTitle(rlevels[level])))
		fmt.Println(v...)
	}
}

func (l *Logger) Debug(v ...interface{}) {
	l.log(DEBUG, v...)
}

func (l *Logger) Notice(v ...interface{}) {
	l.log(NOTICE, v...)
}

func (l *Logger) Warning(v ...interface{}) {
	l.log(WARNING, v...)
}

func (l *Logger) Error(v ...interface{}) {
	l.log(ERROR, v...)
}

func (l *Logger) Critical(v ...interface{}) {
	l.log(CRITICAL, v...)
}
<commit_msg>Add mutex to avoid concurrent writes to stdout<commit_after>/*
	Simple logger package for go services supervised by DJB deamontools
*/

package logger

import (
	"fmt"
	"strings"
	"sync"
	"time"
)

const (
	CRITICAL = 4
	ERROR    = 3
	WARNING  = 2
	NOTICE   = 1
	DEBUG    = 0
)

type Logger struct {
	level        int
	addTimestamp bool
}

type logWritter struct {
	sync.Mutex
}

func (lw *logWritter) WriteLog(msg string) {
	lw.Lock()
	fmt.Println(msg)
	lw.Unlock()
}

var levels = map[string]int{
	"critical": CRITICAL,
	"error":    ERROR,
	"warning":  WARNING,
	"notice":   NOTICE,
	"debug":    DEBUG,
}

var rlevels = [5]string{
	"debug",
	"notice",
	"warning",
	"error",
	"critical",
}

var lw *logWritter

//var fout *bufio.Writer

func init() {
	lw = new(logWritter)
}

func (l *Logger) SetLevel(level string) {
	level = strings.ToLower(level)
	l.level = levels[level]
}

func (l *Logger) SetTimeStamp(ts bool) {
	l.addTimestamp = ts
}

// Base logger
func (l *Logger) log(level int, v ...interface{}) {
	msg := ""
	if level >= l.level {
		if l.addTimestamp {
			msg = fmt.Sprintf("%d - ", time.Now().UnixNano())
		}
		msg = fmt.Sprintf("%s%s - ", msg, strings.ToTitle(rlevels[level]))
		for i := range v {
			msg = fmt.Sprintf("%s%v", msg, v[i])
		}
		lw.WriteLog(msg)
	}
}

func (l *Logger) Debug(v ...interface{}) {
	l.log(DEBUG, v...)
}

func (l *Logger) Notice(v ...interface{}) {
	l.log(NOTICE, v...)
}

func (l *Logger) Warning(v ...interface{}) {
	l.log(WARNING, v...)
}

func (l *Logger) Error(v ...interface{}) {
	l.log(ERROR, v...)
}

func (l *Logger) Critical(v ...interface{}) {
	l.log(CRITICAL, v...)
}
<|endoftext|>


<commit_before>package log

import (
	"bytes"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"runtime"
	"strings"
	"sync"
	"sync/atomic"
	"time"

	"github.com/pkg/errors"
)

var (
	pool = &sync.Pool{
		New: func() interface{} {
			return make([]byte, 0, maxLogSize)
		},
	}

	utsname string
)

func init() {
	hname, err := os.Hostname()
	if err != nil {
		panic(err)
	}
	utsname = hname
}

// Logger is a collection of properties how to output logs.
// Properties are initially set by NewLogger.  They can be customized
// later by Logger methods.
type Logger struct {
	topic        atomic.Value
	threshold    int32
	defaults     atomic.Value
	format       atomic.Value
	errorHandler atomic.Value

	mu     sync.Mutex
	output io.Writer
}

// NewLogger constructs a new Logger struct.
//
// Attributes are initialized as follows:
//    Topic:        path.Base(os.Args[0])
//    Threshold:    LvInfo
//    Formatter:    PlainFormat
//    Output:       os.Stderr
//    Defaults:     nil
//    ErrorHandler: os.Exit(5) on EPIPE.
func NewLogger() *Logger {
	l := &Logger{
		output: os.Stderr,
	}
	filename := filepath.Base(os.Args[0])
	if runtime.GOOS == "windows" {
		if ext := filepath.Ext(filename); ext != "" {
			filename = filename[:len(filename)-len(ext)]
		}
	}
	l.SetTopic(normalizeTopic(filename))
	l.SetThreshold(LvInfo)
	l.SetDefaults(nil)
	l.SetFormatter(PlainFormat{})
	l.SetErrorHandler(errorHandler)
	return l
}

func normalizeTopic(n string) string {
	// Topic must match [.a-z0-9-]+
	topic := strings.Map(func(r rune) rune {
		switch {
		case r == '.' || r == '-':
			return r
		case r >= '0' && r < '9':
			return r
		case r >= 'a' && r <= 'z':
			return r
		case r >= 'A' && r < 'Z':
			return r + ('a' - 'A')
		default:
			return '-'
		}
	}, n)
	if len(topic) > maxTopicLength {
		return topic[:maxTopicLength]
	}
	return topic
}

// Topic returns the topic for the logger.
func (l *Logger) Topic() string {
	return l.topic.Load().(string)
}

// SetTopic sets a new topic for the logger.
// topic must not be empty.  Too long topic may be shortened automatically.
func (l *Logger) SetTopic(topic string) {
	if len(topic) == 0 {
		panic("Empty tag")
	}

	l.topic.Store(topic)
}

// Threshold returns the current threshold of the logger.
func (l *Logger) Threshold() int {
	return int(atomic.LoadInt32(&l.threshold))
}

// Enabled returns true if the log for the given level will be logged.
// This can be used to avoid futile computation for logs being ignored.
//
//    if log.Enabled(log.LvDebug) {
//        log.Debug("message", map[string]interface{}{
//            "debug info": "...",
//        })
//    }
func (l *Logger) Enabled(level int) bool {
	return level <= l.Threshold()
}

// SetThreshold sets the threshold for the logger.
// level must be a pre-defined constant such as LvInfo.
func (l *Logger) SetThreshold(level int) {
	atomic.StoreInt32(&l.threshold, int32(level))
}

// SetThresholdByName sets the threshold for the logger by the level name.
func (l *Logger) SetThresholdByName(n string) error {
	var level int
	switch n {
	case "critical", "crit":
		level = LvCritical
	case "error":
		level = LvError
	case "warning", "warn":
		level = LvWarn
	case "information", "info":
		level = LvInfo
	case "debug":
		level = LvDebug
	default:
		return fmt.Errorf("No such level: %s", n)
	}
	l.SetThreshold(level)
	return nil
}

// SetDefaults sets default field values for the logger.
// Setting nil effectively clear the defaults.
func (l *Logger) SetDefaults(d map[string]interface{}) error {
	for key := range d {
		if !IsValidKey(key) {
			return ErrInvalidKey
		}
	}

	l.defaults.Store(d)
	return nil
}

// Defaults returns default field values.
func (l *Logger) Defaults() map[string]interface{} {
	return l.defaults.Load().(map[string]interface{})
}

// SetFormatter sets log formatter.
func (l *Logger) SetFormatter(f Formatter) {
	l.format.Store(&f)
}

// Formatter returns the current log formatter.
func (l *Logger) Formatter() Formatter {
	return *l.format.Load().(*Formatter)
}

// SetErrorHandler sets error handler.
//
// The handler will be called if the underlying Writer's Write
// returns non-nil error.  If h is nil, no handler will be called.
func (l *Logger) SetErrorHandler(h func(error) error) {
	l.errorHandler.Store(h)
}

// Formatter returns the current log formatter.
func (l *Logger) handleError(err error) error {
	h := l.errorHandler.Load().(func(error) error)
	if h == nil {
		return err
	}
	return h(err)
}

// SetOutput sets io.Writer for log output.
// Setting nil disables log output.
func (l *Logger) SetOutput(w io.Writer) {
	l.mu.Lock()
	l.output = w
	l.mu.Unlock()
}

type logWriter struct {
	buf     []byte
	logfunc func(p []byte) (n int, err error)
}

func (w *logWriter) Write(p []byte) (int, error) {
	tbuf := p
	if len(w.buf) > 0 {
		tbuf = append(w.buf, p...)
	}
	written, err := w.logfunc(tbuf)
	n := written - len(w.buf)
	if err != nil {
		if n < 0 {
			return 0, err
		}
		return n, err
	}

	w.buf = w.buf[:0]
	remain := len(tbuf) - written
	if remain == 0 {
		return n, nil
	}
	if cap(w.buf) < remain {
		return n, errors.New("too long")
	}
	w.buf = append(w.buf, tbuf[n:]...)
	return len(p), nil
}

// Writer returns an io.Writer.
// Each line written in the writer will be logged to the logger
// with the given severity.
func (l *Logger) Writer(severity int) io.Writer {
	logfunc := func(p []byte) (n int, err error) {
		for len(p) > 0 {
			eol := bytes.IndexByte(p, '\n')
			if eol == -1 {
				return
			}
			ln := eol + 1
			err = l.Log(severity, string(p[:eol]), nil)
			if err != nil {
				return
			}
			n += ln
			p = p[ln:]
		}
		return
	}

	return &logWriter{
		buf:     make([]byte, 0, maxLogSize/2),
		logfunc: logfunc,
	}
}

// Log outputs a log message with additional fields.
// fields can be nil.
func (l *Logger) Log(severity int, msg string, fields map[string]interface{}) error {
	if severity > l.Threshold() {
		return nil
	}

	// format the message before acquiring mutex for better concurrency.
	t := time.Now()
	buf := pool.Get().([]byte)
	defer pool.Put(buf)

	b, err := l.Formatter().Format(buf, l, t, severity, msg, fields)
	if err != nil {
		return err
	}

	l.mu.Lock()
	defer l.mu.Unlock()

	if l.output == nil {
		return nil
	}

	_, err = l.output.Write(b)
	if err == nil {
		return nil
	}
	err = l.handleError(err)
	if err == nil {
		return nil
	}
	return errors.Wrap(err, "Logger.Log")
}

// Critical outputs a critical log.
// fields can be nil.
func (l *Logger) Critical(msg string, fields map[string]interface{}) error {
	return l.Log(LvCritical, msg, fields)
}

// Error outputs an error log.
// fields can be nil.
func (l *Logger) Error(msg string, fields map[string]interface{}) error {
	return l.Log(LvError, msg, fields)
}

// Warn outputs a warning log.
// fields can be nil.
func (l *Logger) Warn(msg string, fields map[string]interface{}) error {
	return l.Log(LvWarn, msg, fields)
}

// Info outputs an informational log.
// fields can be nil.
func (l *Logger) Info(msg string, fields map[string]interface{}) error {
	return l.Log(LvInfo, msg, fields)
}

// Debug outputs a debug log.
// fields can be nil.
func (l *Logger) Debug(msg string, fields map[string]interface{}) error {
	return l.Log(LvDebug, msg, fields)
}

// WriteThrough writes data through to the underlying writer.
func (l *Logger) WriteThrough(data []byte) error {
	l.mu.Lock()
	_, err := l.output.Write(data)
	l.mu.Unlock()
	return err
}
<commit_msg>Handle errors in Logger.WriteThrough<commit_after>package log

import (
	"bytes"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"runtime"
	"strings"
	"sync"
	"sync/atomic"
	"time"

	"github.com/pkg/errors"
)

var (
	pool = &sync.Pool{
		New: func() interface{} {
			return make([]byte, 0, maxLogSize)
		},
	}

	utsname string
)

func init() {
	hname, err := os.Hostname()
	if err != nil {
		panic(err)
	}
	utsname = hname
}

// Logger is a collection of properties how to output logs.
// Properties are initially set by NewLogger.  They can be customized
// later by Logger methods.
type Logger struct {
	topic        atomic.Value
	threshold    int32
	defaults     atomic.Value
	format       atomic.Value
	errorHandler atomic.Value

	mu     sync.Mutex
	output io.Writer
}

// NewLogger constructs a new Logger struct.
//
// Attributes are initialized as follows:
//    Topic:        path.Base(os.Args[0])
//    Threshold:    LvInfo
//    Formatter:    PlainFormat
//    Output:       os.Stderr
//    Defaults:     nil
//    ErrorHandler: os.Exit(5) on EPIPE.
func NewLogger() *Logger {
	l := &Logger{
		output: os.Stderr,
	}
	filename := filepath.Base(os.Args[0])
	if runtime.GOOS == "windows" {
		if ext := filepath.Ext(filename); ext != "" {
			filename = filename[:len(filename)-len(ext)]
		}
	}
	l.SetTopic(normalizeTopic(filename))
	l.SetThreshold(LvInfo)
	l.SetDefaults(nil)
	l.SetFormatter(PlainFormat{})
	l.SetErrorHandler(errorHandler)
	return l
}

func normalizeTopic(n string) string {
	// Topic must match [.a-z0-9-]+
	topic := strings.Map(func(r rune) rune {
		switch {
		case r == '.' || r == '-':
			return r
		case r >= '0' && r < '9':
			return r
		case r >= 'a' && r <= 'z':
			return r
		case r >= 'A' && r < 'Z':
			return r + ('a' - 'A')
		default:
			return '-'
		}
	}, n)
	if len(topic) > maxTopicLength {
		return topic[:maxTopicLength]
	}
	return topic
}

// Topic returns the topic for the logger.
func (l *Logger) Topic() string {
	return l.topic.Load().(string)
}

// SetTopic sets a new topic for the logger.
// topic must not be empty.  Too long topic may be shortened automatically.
func (l *Logger) SetTopic(topic string) {
	if len(topic) == 0 {
		panic("Empty tag")
	}

	l.topic.Store(topic)
}

// Threshold returns the current threshold of the logger.
func (l *Logger) Threshold() int {
	return int(atomic.LoadInt32(&l.threshold))
}

// Enabled returns true if the log for the given level will be logged.
// This can be used to avoid futile computation for logs being ignored.
//
//    if log.Enabled(log.LvDebug) {
//        log.Debug("message", map[string]interface{}{
//            "debug info": "...",
//        })
//    }
func (l *Logger) Enabled(level int) bool {
	return level <= l.Threshold()
}

// SetThreshold sets the threshold for the logger.
// level must be a pre-defined constant such as LvInfo.
func (l *Logger) SetThreshold(level int) {
	atomic.StoreInt32(&l.threshold, int32(level))
}

// SetThresholdByName sets the threshold for the logger by the level name.
func (l *Logger) SetThresholdByName(n string) error {
	var level int
	switch n {
	case "critical", "crit":
		level = LvCritical
	case "error":
		level = LvError
	case "warning", "warn":
		level = LvWarn
	case "information", "info":
		level = LvInfo
	case "debug":
		level = LvDebug
	default:
		return fmt.Errorf("No such level: %s", n)
	}
	l.SetThreshold(level)
	return nil
}

// SetDefaults sets default field values for the logger.
// Setting nil effectively clear the defaults.
func (l *Logger) SetDefaults(d map[string]interface{}) error {
	for key := range d {
		if !IsValidKey(key) {
			return ErrInvalidKey
		}
	}

	l.defaults.Store(d)
	return nil
}

// Defaults returns default field values.
func (l *Logger) Defaults() map[string]interface{} {
	return l.defaults.Load().(map[string]interface{})
}

// SetFormatter sets log formatter.
func (l *Logger) SetFormatter(f Formatter) {
	l.format.Store(&f)
}

// Formatter returns the current log formatter.
func (l *Logger) Formatter() Formatter {
	return *l.format.Load().(*Formatter)
}

// SetErrorHandler sets error handler.
//
// The handler will be called if the underlying Writer's Write
// returns non-nil error.  If h is nil, no handler will be called.
func (l *Logger) SetErrorHandler(h func(error) error) {
	l.errorHandler.Store(h)
}

// Formatter returns the current log formatter.
func (l *Logger) handleError(err error) error {
	h := l.errorHandler.Load().(func(error) error)
	if h == nil {
		return err
	}
	return h(err)
}

// SetOutput sets io.Writer for log output.
// Setting nil disables log output.
func (l *Logger) SetOutput(w io.Writer) {
	l.mu.Lock()
	l.output = w
	l.mu.Unlock()
}

type logWriter struct {
	buf     []byte
	logfunc func(p []byte) (n int, err error)
}

func (w *logWriter) Write(p []byte) (int, error) {
	tbuf := p
	if len(w.buf) > 0 {
		tbuf = append(w.buf, p...)
	}
	written, err := w.logfunc(tbuf)
	n := written - len(w.buf)
	if err != nil {
		if n < 0 {
			return 0, err
		}
		return n, err
	}

	w.buf = w.buf[:0]
	remain := len(tbuf) - written
	if remain == 0 {
		return n, nil
	}
	if cap(w.buf) < remain {
		return n, errors.New("too long")
	}
	w.buf = append(w.buf, tbuf[n:]...)
	return len(p), nil
}

// Writer returns an io.Writer.
// Each line written in the writer will be logged to the logger
// with the given severity.
func (l *Logger) Writer(severity int) io.Writer {
	logfunc := func(p []byte) (n int, err error) {
		for len(p) > 0 {
			eol := bytes.IndexByte(p, '\n')
			if eol == -1 {
				return
			}
			ln := eol + 1
			err = l.Log(severity, string(p[:eol]), nil)
			if err != nil {
				return
			}
			n += ln
			p = p[ln:]
		}
		return
	}

	return &logWriter{
		buf:     make([]byte, 0, maxLogSize/2),
		logfunc: logfunc,
	}
}

// Log outputs a log message with additional fields.
// fields can be nil.
func (l *Logger) Log(severity int, msg string, fields map[string]interface{}) error {
	if severity > l.Threshold() {
		return nil
	}

	// format the message before acquiring mutex for better concurrency.
	t := time.Now()
	buf := pool.Get().([]byte)
	defer pool.Put(buf)

	b, err := l.Formatter().Format(buf, l, t, severity, msg, fields)
	if err != nil {
		return err
	}

	l.mu.Lock()
	defer l.mu.Unlock()

	if l.output == nil {
		return nil
	}

	_, err = l.output.Write(b)
	if err == nil {
		return nil
	}
	err = l.handleError(err)
	if err == nil {
		return nil
	}
	return errors.Wrap(err, "Logger.Log")
}

// Critical outputs a critical log.
// fields can be nil.
func (l *Logger) Critical(msg string, fields map[string]interface{}) error {
	return l.Log(LvCritical, msg, fields)
}

// Error outputs an error log.
// fields can be nil.
func (l *Logger) Error(msg string, fields map[string]interface{}) error {
	return l.Log(LvError, msg, fields)
}

// Warn outputs a warning log.
// fields can be nil.
func (l *Logger) Warn(msg string, fields map[string]interface{}) error {
	return l.Log(LvWarn, msg, fields)
}

// Info outputs an informational log.
// fields can be nil.
func (l *Logger) Info(msg string, fields map[string]interface{}) error {
	return l.Log(LvInfo, msg, fields)
}

// Debug outputs a debug log.
// fields can be nil.
func (l *Logger) Debug(msg string, fields map[string]interface{}) error {
	return l.Log(LvDebug, msg, fields)
}

// WriteThrough writes data through to the underlying writer.
func (l *Logger) WriteThrough(data []byte) error {
	l.mu.Lock()
	defer l.mu.Unlock()

	_, err := l.output.Write(data)
	if err == nil {
		return nil
	}
	err = l.handleError(err)
	if err == nil {
		return nil
	}
	return errors.Wrap(err, "Logger.WriteThrough")
}
<|endoftext|>


<commit_before>// logger
package minilog

import (
	"bufio"
	"bytes"
	"fmt"
	"io"
	"os"
	"os/user"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"
	"sync"
	"time"
)

var logger *Logger

type severity int

const (
	traceLevel severity = iota
	debugLevel
	infoLevel
	warnLevel
	errorLevel
	fatalLevel
	numSeverity = 6
)

var severityName = []string{
	traceLevel: "TRACE",
	debugLevel: "DEBUG",
	infoLevel:  "INFO ",
	warnLevel:  "WARN ",
	errorLevel: "ERROR",
	fatalLevel: "FATAL",
}

type Mode int

const (
	ToFile       = (1 << 0)
	ToStderr     = (1 << 1)
	AlsoToStderr = (ToFile | ToStderr)
)

func (m Mode) String() string {
	switch m {
	case ToFile:
		return "log to file"
	case ToStderr:
		return "print to stderr"
	case AlsoToStderr:
		return "log to file and print to stderr"
	default:
		return "unknow log mode"
	}
}

type Logger struct {
	maxSize    int
	maxFileNum int
	level      severity
	logDir     string
	logMode    Mode
	logName    string
	prevLog    *lastLog
	keepName   []string
	createTime int64
	callHeader func(string) string
	buffer     *logBuffer
	mu         sync.Mutex
	writer     flushWriter
	nBytes     int
}

type lastLog struct {
	repeatLog  string
	repeatNum  int
	lastHeader string
}

var (
	pid      = os.Getpid()
	program  = filepath.Base(os.Args[0])
	host     = "unknownhost"
	userName = "unknownuser"
)

func init() {
	h, err := os.Hostname()
	if err == nil {
		host = shortHostname(h)
	}

	current, err := user.Current()
	if err == nil {
		userName = current.Username
	}

	// Sanitize userName since it may contain filepath separators on Windows.
	userName = strings.Replace(userName, `\`, "_", -1)
}

func shortHostname(hostname string) string {
	if i := strings.Index(hostname, "."); i >= 0 {
		return hostname[:i]
	}
	return hostname
}

func severityByName(s string) severity {
	s = strings.ToUpper(s)
	for i, name := range severityName {
		if name == s {
			return severity(i)
		}
	}
	return warnLevel
}

// init logger for log
func InitLogger() *Logger {
	l := new(Logger)
	l.maxSize = 0
	l.maxFileNum = 1
	l.level = warnLevel
	l.logDir = os.TempDir()
	l.logMode = 0
	l.prevLog = new(lastLog)
	l.logName = program + ".log." + host
	l.SetLogHeader(l.formatHeader)
	l.nBytes = 0
	logger = l

	return l
}
func CloseLogger() {
	logger.close()
}

// print logger config
func (l *Logger) PrintLogger() {
	fmt.Printf("logDir<%s>, logName<%s>, level<%s>, maxSize<%d kb>, maxFileNum<%d>, logMode=%s\n",
		l.logDir, l.logName, severityName[l.level],
		l.maxSize/1024, l.maxFileNum,
		l.logMode)
}

// file max size
func (l *Logger) SetFileMaxSize(maxSize int) {
	if maxSize < 0 {
		return
	}
	l.maxSize = maxSize * 1024
}
func (l *Logger) getFileMaxSize() int {
	return l.maxSize
}

// max file num
func (l *Logger) SetMaxFileNum(maxFileNum int) {
	if maxFileNum < 0 {
		return
	}

	l.maxFileNum = maxFileNum
}
func (l *Logger) getMaxFileNum() int {
	return l.maxFileNum
}

// log level
func (l *Logger) SetLogLevel(level string) {
	l.level = severityByName(level)
}
func (l *Logger) getLogLevel() severity {
	return l.level
}
func (l *Logger) IsLogFatal() bool {
	if l.level == fatalLevel {
		return true
	}
	return false
}
func (l *Logger) IsLogError() bool {
	if l.level == errorLevel {
		return true
	}
	return false
}
func (l *Logger) IsLogWarn() bool {
	if l.level == warnLevel {
		return true
	}
	return false
}
func (l *Logger) IsLogInfo() bool {
	if l.level == infoLevel {
		return true
	}
	return false
}
func (l *Logger) IsLogDebug() bool {
	if l.level == debugLevel {
		return true
	}
	return false
}
func (l *Logger) IsLogTrace() bool {
	if l.level == traceLevel {
		return true
	}
	return false
}

// log dir
func (l *Logger) SetLogDir(logDir string) {
	err := os.Mkdir(logDir, 0666)
	if err != nil && os.IsNotExist(err) {
		return
	}
	l.logDir = logDir
}
func (l *Logger) getLogDir() string {
	return l.logDir
}

// log mode
func (l *Logger) SetLogMode(logMode Mode) {
	l.logMode = logMode
}
func (l *Logger) getLogMode() Mode {
	return l.logMode
}
func (l *Logger) isLogFileMode() bool {
	if (l.logMode & ToFile) != 0 {

		return true
	}
	return false
}
func (l *Logger) isLogStderrMode() bool {
	if (l.logMode & ToStderr) != 0 {
		return true
	}
	return false
}

// userdef log header
func (l *Logger) SetLogHeader(header func(string) string) {
	l.callHeader = header
}
func (l *Logger) getLogHeader() func(string) string {
	return l.callHeader
}
func (l *Logger) SetLogConfig(logDir, level string, maxSize, maxFileNum int, logMode Mode) {
	l.SetLogDir(logDir)
	l.SetLogLevel(level)
	l.SetFileMaxSize(maxSize)
	l.SetMaxFileNum(maxFileNum)
	l.logMode = logMode
}

// get buffer
func (l *Logger) getBuffer() *logBuffer {
	b := l.buffer
	if b != nil {
		l.buffer = b.next
	}

	if b == nil {
		b = new(logBuffer)
	} else {
		b.next = nil
		b.Reset()
	}

	return b
}

type flushWriter interface {
	Flush() error
	Sync() error
	Fclose() error
	io.Writer
}

type syncBuffer struct {
	*bufio.Writer
	file *os.File
}

func (sbuf *syncBuffer) Sync() error {
	return sbuf.file.Sync()
}
func (sbuf *syncBuffer) Fclose() error {
	return sbuf.file.Close()
}

type logBuffer struct {
	bytes.Buffer
	next *logBuffer
}

// get log filename, funcname and line number
func GetLogFileLine(depth int) (string, string, int) {
	var funcName string
	pc, file, line, ok := runtime.Caller(3 + depth)
	if !ok {
		funcName = "unknow"
		file = "???"
		line = 1
	} else {
		idx := strings.LastIndex(file, "/")
		if idx >= 0 {
			file = file[idx+1:]
		}
		fullName := runtime.FuncForPC(pc).Name()
		shortName := strings.Split(fullName, ".")
		funcName = shortName[len(shortName)-1]
	}

	return funcName, file, line
}

// log header by default
func (l *Logger) formatHeader(level string) string {
	now := time.Now()
	year, month, day := now.Date()
	hour, minute, second := now.Clock()
	usec := now.Nanosecond() / 1000000

	_, file, line := GetLogFileLine(2)
	// yy-mm-dd hh:mm:ss.uuuu level pid file[line]:
	header := fmt.Sprintf("%04d-%02d-%02d %02d:%02d:%02d.%04d [%s] %d %s[%d]",
		year, month, day, hour, minute, second, usec,
		level, os.Getpid(), file, line)

	return header
}

// Trace
func Ltrace(format string, args ...interface{}) {
	logger.println(traceLevel, format, args...)
}

// Debug
func Ldebug(format string, args ...interface{}) {
	logger.println(debugLevel, format, args...)
}

// Info
func Linfo(format string, args ...interface{}) {
	logger.println(infoLevel, format, args...)
}

// Warning
func Lwarn(format string, args ...interface{}) {
	logger.println(warnLevel, format, args...)
}

// Error
func Lerror(format string, args ...interface{}) {
	logger.println(errorLevel, format, args...)
}

// Fatal
func Lfatal(format string, args ...interface{}) {
	logger.println(fatalLevel, format, args...)
}

func (l *Logger) println(s severity, format string, args ...interface{}) {
	if l.level > s && s < numSeverity {
		return
	}

	header := l.callHeader(severityName[s])
	message := fmt.Sprintf(format, args...)

	// equal prev log message
	if l.prevLog.repeatLog == message {
		l.prevLog.repeatNum++
		l.prevLog.lastHeader = header
		return
	} else {
		l.printLastLog()
		l.prevLog.repeatLog = message
		l.prevLog.lastHeader = header
		l.prevLog.repeatNum = 0
	}

	l.output(header, message)
}

// rename file when rolling policy
func rename(fname, kname string) (string, int) {
	var n int = 0
	lenk := len(kname)
	lenf := len(fname)
	if lenk > lenf {
		idx := kname[(lenf + 1):]
		n, _ = strconv.Atoi(idx)
	}

	name := fmt.Sprintf("%s.%d", fname, n+1)
	err := os.Rename(kname, name)
	if err != nil {
		fmt.Println(err)
	}
	return name, n + 1
}

// create log file by default
func (l *Logger) createLogFile() *syncBuffer {
	sBuf := new(syncBuffer)

	fname := filepath.Join(l.logDir, l.logName)

	//
	if l.keepName == nil {
		l.keepName = make([]string, l.maxFileNum)
	}
	length := len(l.keepName)
	for i := length - 1; i >= 0; i-- {
		if len(l.keepName[i]) == 0 {
			continue
		}
		if i == l.maxFileNum-1 {
			os.Remove(l.keepName[i])
			continue
		}
		if i == 0 {
			l.writer.Fclose()
		}
		name, n := rename(fname, l.keepName[i])
		l.keepName[n] = name
	}

	f, err := os.Create(fname)
	if err != nil {
		fmt.Printf("can't create log file<%s>\n", fname)
		os.Exit(-1)
	}
	sBuf.file = f
	sBuf.Writer = bufio.NewWriterSize(f, 1024*1024)
	l.createTime = getCreateTime()
	l.keepName[0] = fname

	return sBuf
}

// get next create time in daily policy of rolling policy by default
func getCreateTime() int64 {
	timeStr := time.Now().Format("2006-01-02 00:00:00")
	t, _ := time.Parse("2006-01-02 00:00:00", timeStr)
	d, _ := time.ParseDuration("+24h")
	return t.Add(d).Unix()
}

func isInToday(createTime int64) bool {
	now := time.Now().Unix()

	if now > createTime {
		return true
	}

	return false
}
func (l *Logger) output(header string, msg string) {
	l.mu.Lock()
	defer l.mu.Unlock()
	buffer := l.getBuffer()
	message := msg

	buffer.WriteString(header)
	buffer.WriteString(": ")
	buffer.WriteString(message)
	buffer.WriteString("\n")
	data := buffer.Bytes()

	// log mode
	if l.isLogStderrMode() {
		os.Stdout.Write(data)
	}
	if l.isLogFileMode() {
		// rolling policy
		// 1, file max size
		// 2, daily
		if l.writer == nil ||
			(l.maxFileNum > 1 && ((l.maxSize == 0 && isInToday(l.createTime)) ||
				(l.maxSize > 0 && l.nBytes > l.maxSize))) {
			l.writer = l.createLogFile()
			l.nBytes = 0
		}
		n, _ := l.writer.Write(data)
		l.writer.Flush()
		l.nBytes += n
	}

	l.putBuffer(buffer)
}

func (l *Logger) printLastLog() {
	if l.prevLog.repeatNum > 0 {
		msg := fmt.Sprintf("Last message repeated %d times", l.prevLog.repeatNum)
		l.output(l.prevLog.lastHeader, msg)
	}
}

func (l *Logger) putBuffer(b *logBuffer) {
	b.next = l.buffer
	l.buffer = b
}

func (l *Logger) close() {
	l.printLastLog()

	if l.writer == nil {
		return
	}
	l.writer.Sync()
	l.writer.Fclose()
}
<commit_msg>update file<commit_after>// logger

//Go support for leveled logs
//
// Copyright 2016 @wren. All Rights Reserved.
//
// 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.

//It provides functions Linfo, Lwarn, Lerror, Lfatal
//Basic examples:

//    l := InitLogger()
//    defer CloseLogger()
//    l.SetLogLevel("trace")
//    l.SetLogMode(ToStderr)
//    l.SetLogDir("/tmp")

//    Ltrace("hello world")
//    Lerror("this is a test")

// It has two rolling policy: file max size and daily on zero
// examples:
//    l := InitLogger()
//    defer CloseLogger()
//    l.SetMaxFileNum(2)   // default daily on zero
//    l.SetFileMaxSize(4)  // use file max size policy to replace file max size policy
//

package minilog

import (
	"bufio"
	"bytes"
	"fmt"
	"io"
	"os"
	"os/user"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"
	"sync"
	"time"
)

var logger *Logger

type severity int

const (
	traceLevel severity = iota
	debugLevel
	infoLevel
	warnLevel
	errorLevel
	fatalLevel
	numSeverity = 6
)

var severityName = []string{
	traceLevel: "TRACE",
	debugLevel: "DEBUG",
	infoLevel:  "INFO ",
	warnLevel:  "WARN ",
	errorLevel: "ERROR",
	fatalLevel: "FATAL",
}

type Mode int

const (
	ToFile       = (1 << 0)
	ToStderr     = (1 << 1)
	AlsoToStderr = (ToFile | ToStderr)
)

func (m Mode) String() string {
	switch m {
	case ToFile:
		return "log to file"
	case ToStderr:
		return "print to stderr"
	case AlsoToStderr:
		return "log to file and print to stderr"
	default:
		return "unknow log mode"
	}
}

type Logger struct {
	maxSize    int
	maxFileNum int
	level      severity
	logDir     string
	logMode    Mode
	logName    string
	prevLog    *lastLog
	keepName   []string
	createTime int64
	callHeader func(string) string
	buffer     *logBuffer
	mu         sync.Mutex
	writer     flushWriter
	nBytes     int
}

type lastLog struct {
	repeatLog  string
	repeatNum  int
	lastHeader string
}

var (
	pid      = os.Getpid()
	program  = filepath.Base(os.Args[0])
	host     = "unknownhost"
	userName = "unknownuser"
)

func init() {
	h, err := os.Hostname()
	if err == nil {
		host = shortHostname(h)
	}

	current, err := user.Current()
	if err == nil {
		userName = current.Username
	}

	// Sanitize userName since it may contain filepath separators on Windows.
	userName = strings.Replace(userName, `\`, "_", -1)
}

func shortHostname(hostname string) string {
	if i := strings.Index(hostname, "."); i >= 0 {
		return hostname[:i]
	}
	return hostname
}

func severityByName(s string) severity {
	s = strings.ToUpper(s)
	for i, name := range severityName {
		if name == s {
			return severity(i)
		}
	}
	return warnLevel
}

// init logger for log
func InitLogger() *Logger {
	l := new(Logger)
	l.maxSize = 0
	l.maxFileNum = 1
	l.level = warnLevel
	l.logDir = os.TempDir()
	l.logMode = 0
	l.prevLog = new(lastLog)
	l.logName = program + ".log." + host
	l.SetLogHeader(l.formatHeader)
	l.nBytes = 0
	logger = l

	return l
}
func CloseLogger() {
	logger.close()
}

// print logger config
func (l *Logger) PrintLogger() {
	fmt.Printf("logDir<%s>, logName<%s>, level<%s>, maxSize<%d kb>, maxFileNum<%d>, logMode=%s\n",
		l.logDir, l.logName, severityName[l.level],
		l.maxSize/1024, l.maxFileNum,
		l.logMode)
}

// file max size
func (l *Logger) SetFileMaxSize(maxSize int) {
	if maxSize < 0 {
		return
	}
	l.maxSize = maxSize * 1024
}
func (l *Logger) getFileMaxSize() int {
	return l.maxSize
}

// max file num
func (l *Logger) SetMaxFileNum(maxFileNum int) {
	if maxFileNum < 0 {
		return
	}

	l.maxFileNum = maxFileNum
}
func (l *Logger) getMaxFileNum() int {
	return l.maxFileNum
}

// log level
func (l *Logger) SetLogLevel(level string) {
	l.level = severityByName(level)
}
func (l *Logger) getLogLevel() severity {
	return l.level
}
func (l *Logger) IsLogFatal() bool {
	if l.level == fatalLevel {
		return true
	}
	return false
}
func (l *Logger) IsLogError() bool {
	if l.level == errorLevel {
		return true
	}
	return false
}
func (l *Logger) IsLogWarn() bool {
	if l.level == warnLevel {
		return true
	}
	return false
}
func (l *Logger) IsLogInfo() bool {
	if l.level == infoLevel {
		return true
	}
	return false
}
func (l *Logger) IsLogDebug() bool {
	if l.level == debugLevel {
		return true
	}
	return false
}
func (l *Logger) IsLogTrace() bool {
	if l.level == traceLevel {
		return true
	}
	return false
}

// log dir
func (l *Logger) SetLogDir(logDir string) {
	err := os.Mkdir(logDir, 0666)
	if err != nil && os.IsNotExist(err) {
		return
	}
	l.logDir = logDir
}
func (l *Logger) getLogDir() string {
	return l.logDir
}

// log mode
func (l *Logger) SetLogMode(logMode Mode) {
	l.logMode = logMode
}
func (l *Logger) getLogMode() Mode {
	return l.logMode
}
func (l *Logger) isLogFileMode() bool {
	if (l.logMode & ToFile) != 0 {

		return true
	}
	return false
}
func (l *Logger) isLogStderrMode() bool {
	if (l.logMode & ToStderr) != 0 {
		return true
	}
	return false
}

// userdef log header
func (l *Logger) SetLogHeader(header func(string) string) {
	l.callHeader = header
}
func (l *Logger) getLogHeader() func(string) string {
	return l.callHeader
}
func (l *Logger) SetLogConfig(logDir, level string, maxSize, maxFileNum int, logMode Mode) {
	l.SetLogDir(logDir)
	l.SetLogLevel(level)
	l.SetFileMaxSize(maxSize)
	l.SetMaxFileNum(maxFileNum)
	l.logMode = logMode
}

// get buffer
func (l *Logger) getBuffer() *logBuffer {
	b := l.buffer
	if b != nil {
		l.buffer = b.next
	}

	if b == nil {
		b = new(logBuffer)
	} else {
		b.next = nil
		b.Reset()
	}

	return b
}

type flushWriter interface {
	Flush() error
	Sync() error
	Fclose() error
	io.Writer
}

type syncBuffer struct {
	*bufio.Writer
	file *os.File
}

func (sbuf *syncBuffer) Sync() error {
	return sbuf.file.Sync()
}
func (sbuf *syncBuffer) Fclose() error {
	return sbuf.file.Close()
}

type logBuffer struct {
	bytes.Buffer
	next *logBuffer
}

// get log filename, funcname and line number
func GetLogFileLine(depth int) (string, string, int) {
	var funcName string
	pc, file, line, ok := runtime.Caller(3 + depth)
	if !ok {
		funcName = "unknow"
		file = "???"
		line = 1
	} else {
		idx := strings.LastIndex(file, "/")
		if idx >= 0 {
			file = file[idx+1:]
		}
		fullName := runtime.FuncForPC(pc).Name()
		shortName := strings.Split(fullName, ".")
		funcName = shortName[len(shortName)-1]
	}

	return funcName, file, line
}

// log header by default
func (l *Logger) formatHeader(level string) string {
	now := time.Now()
	year, month, day := now.Date()
	hour, minute, second := now.Clock()
	usec := now.Nanosecond() / 1000000

	_, file, line := GetLogFileLine(2)
	// yy-mm-dd hh:mm:ss.uuuu level pid file[line]:
	header := fmt.Sprintf("%04d-%02d-%02d %02d:%02d:%02d.%04d [%s] %d %s[%d]",
		year, month, day, hour, minute, second, usec,
		level, os.Getpid(), file, line)

	return header
}

// Trace
func Ltrace(format string, args ...interface{}) {
	logger.println(traceLevel, format, args...)
}

// Debug
func Ldebug(format string, args ...interface{}) {
	logger.println(debugLevel, format, args...)
}

// Info
func Linfo(format string, args ...interface{}) {
	logger.println(infoLevel, format, args...)
}

// Warning
func Lwarn(format string, args ...interface{}) {
	logger.println(warnLevel, format, args...)
}

// Error
func Lerror(format string, args ...interface{}) {
	logger.println(errorLevel, format, args...)
}

// Fatal
func Lfatal(format string, args ...interface{}) {
	logger.println(fatalLevel, format, args...)
}

func (l *Logger) println(s severity, format string, args ...interface{}) {
	if l.level > s && s < numSeverity {
		return
	}

	header := l.callHeader(severityName[s])
	message := fmt.Sprintf(format, args...)

	// equal prev log message
	if l.prevLog.repeatLog == message {
		l.prevLog.repeatNum++
		l.prevLog.lastHeader = header
		return
	} else {
		l.printLastLog()
		l.prevLog.repeatLog = message
		l.prevLog.lastHeader = header
		l.prevLog.repeatNum = 0
	}

	l.output(header, message)
}

// rename file when rolling policy
func rename(fname, kname string) (string, int) {
	var n int = 0
	lenk := len(kname)
	lenf := len(fname)
	if lenk > lenf {
		idx := kname[(lenf + 1):]
		n, _ = strconv.Atoi(idx)
	}

	name := fmt.Sprintf("%s.%d", fname, n+1)
	err := os.Rename(kname, name)
	if err != nil {
		fmt.Println(err)
	}
	return name, n + 1
}

// create log file by default
func (l *Logger) createLogFile() *syncBuffer {
	sBuf := new(syncBuffer)

	fname := filepath.Join(l.logDir, l.logName)

	//
	if l.keepName == nil {
		l.keepName = make([]string, l.maxFileNum)
	}
	length := len(l.keepName)
	for i := length - 1; i >= 0; i-- {
		if len(l.keepName[i]) == 0 {
			continue
		}
		if i == l.maxFileNum-1 {
			os.Remove(l.keepName[i])
			continue
		}
		if i == 0 {
			l.writer.Fclose()
		}
		name, n := rename(fname, l.keepName[i])
		l.keepName[n] = name
	}

	f, err := os.Create(fname)
	if err != nil {
		fmt.Printf("can't create log file<%s>\n", fname)
		os.Exit(-1)
	}
	sBuf.file = f
	sBuf.Writer = bufio.NewWriterSize(f, 1024*1024)
	l.createTime = getCreateTime()
	l.keepName[0] = fname

	return sBuf
}

// get next create time in daily policy of rolling policy by default
func getCreateTime() int64 {
	timeStr := time.Now().Format("2006-01-02 00:00:00")
	t, _ := time.Parse("2006-01-02 00:00:00", timeStr)
	d, _ := time.ParseDuration("+24h")
	return t.Add(d).Unix()
}

func isInToday(createTime int64) bool {
	now := time.Now().Unix()

	if now > createTime {
		return true
	}

	return false
}
func (l *Logger) output(header string, msg string) {
	l.mu.Lock()
	defer l.mu.Unlock()
	buffer := l.getBuffer()
	message := msg

	buffer.WriteString(header)
	buffer.WriteString(": ")
	buffer.WriteString(message)
	buffer.WriteString("\n")
	data := buffer.Bytes()

	// log mode
	if l.isLogStderrMode() {
		os.Stdout.Write(data)
	}
	if l.isLogFileMode() {
		// rolling policy
		// 1, file max size
		// 2, daily
		if l.writer == nil ||
			(l.maxFileNum > 1 && ((l.maxSize == 0 && isInToday(l.createTime)) ||
				(l.maxSize > 0 && l.nBytes > l.maxSize))) {
			l.writer = l.createLogFile()
			l.nBytes = 0
		}
		n, _ := l.writer.Write(data)
		l.writer.Flush()
		l.nBytes += n
	}

	l.putBuffer(buffer)
}

func (l *Logger) printLastLog() {
	if l.prevLog.repeatNum > 0 {
		msg := fmt.Sprintf("Last message repeated %d times", l.prevLog.repeatNum)
		l.output(l.prevLog.lastHeader, msg)
	}
}

func (l *Logger) putBuffer(b *logBuffer) {
	b.next = l.buffer
	l.buffer = b
}

func (l *Logger) close() {
	l.printLastLog()

	if l.writer == nil {
		return
	}
	l.writer.Sync()
	l.writer.Fclose()
}
<|endoftext|>


<commit_before>// Copyright 2013 bee authors
//
// 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 main

import (
	"errors"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"runtime"
	"sync"
	"sync/atomic"
	"text/template"
)

var errInvalidLogLevel = errors.New("logger: invalid log level")

const (
	levelCritical = iota
	levelFatal
	levelSuccess
	levelHint
	levelDebug
	levelInfo
	levelWarn
	levelError
)

var (
	sequenceNo uint64
	logger     *BeeLogger
)

// BeeLogger logs logging records to the specified io.Writer
type BeeLogger struct {
	mu     sync.Mutex
	output io.Writer
}

// LogRecord represents a log record and contains the timestamp when the record
// was created, an increasing id, level and the actual formatted log line.
type LogRecord struct {
	ID       uint64
	Level    string
	Message  string
	Filename string
	LineNo   int
}

var (
	logRecordTemplate      *template.Template
	debugLogRecordTemplate *template.Template
	debugLogFormat         string
)

func init() {
	var (
		err             error
		simpleLogFormat = `{{Now "2006/01/02 15:04:05"}} {{.Level}} ▶ {{.ID}} {{.Message}}{{EndLine}}`
		debugLogFormat  = `{{Now "2006/01/02 15:04:05"}} {{.Level}} ▶ {{.ID}} {{.Filename}}:{{.LineNo}} {{.Message}}{{EndLine}}`
	)

	// Initialize and parse logging templates
	funcs := template.FuncMap{
		"Now":     Now,
		"EndLine": EndLine,
	}
	logRecordTemplate, err = template.New("logRecordTemplate").Funcs(funcs).Parse(simpleLogFormat)
	MustCheck(err)
	debugLogRecordTemplate, err = template.New("dbgLogRecordTemplate").Funcs(funcs).Parse(debugLogFormat)
	MustCheck(err)

	// Initialize the logger instance with a NewColorWriter output
	logger = &BeeLogger{output: NewColorWriter(os.Stdout)}
}

// SetOutput sets the logger output destination
func (l *BeeLogger) SetOutput(w io.Writer) {
	l.mu.Lock()
	defer l.mu.Unlock()
	l.output = NewColorWriter(w)
}

func (l *BeeLogger) getLevelTag(level int) string {
	switch level {
	case levelFatal:
		return "FATAL   "
	case levelSuccess:
		return "SUCCESS "
	case levelHint:
		return "HINT    "
	case levelDebug:
		return "DEBUG   "
	case levelInfo:
		return "INFO    "
	case levelWarn:
		return "WARN    "
	case levelError:
		return "ERROR   "
	case levelCritical:
		return "CRITICAL"
	default:
		panic(errInvalidLogLevel)
	}
}

func (l *BeeLogger) getColorLevel(level int) string {
	switch level {
	case levelCritical:
		return RedBold(l.getLevelTag(level))
	case levelFatal:
		return RedBold(l.getLevelTag(level))
	case levelInfo:
		return BlueBold(l.getLevelTag(level))
	case levelHint:
		return CyanBold(l.getLevelTag(level))
	case levelDebug:
		return YellowBold(l.getLevelTag(level))
	case levelError:
		return RedBold(l.getLevelTag(level))
	case levelWarn:
		return YellowBold(l.getLevelTag(level))
	case levelSuccess:
		return GreenBold(l.getLevelTag(level))
	default:
		panic(errInvalidLogLevel)
	}
}

// mustLog logs the message according to the specified level and arguments.
// It panics in case of an error.
func (l *BeeLogger) mustLog(level int, message string, args ...interface{}) {
	// Create the logging record and pass into the output
	record := LogRecord{
		ID:      atomic.AddUint64(&sequenceNo, 1),
		Level:   l.getColorLevel(level),
		Message: fmt.Sprintf(message, args...),
	}

	err := logRecordTemplate.Execute(l.output, record)
	MustCheck(err)
}

// mustLogDebug logs a debug message only if debug mode
// is enabled. i.e. DEBUG_ENABLED="1"
func (l *BeeLogger) mustLogDebug(message string, args ...interface{}) {
	if !IsDebugEnabled() {
		return
	}

	// Change the output to Stderr
	l.SetOutput(os.Stderr)

	// Create the log record and Get the filename
	// and the line number of the caller
	_, file, line, _ := runtime.Caller(1)
	record := LogRecord{
		ID:       atomic.AddUint64(&sequenceNo, 1),
		Level:    l.getColorLevel(levelDebug),
		Message:  fmt.Sprintf(message, args...),
		LineNo:   line,
		Filename: filepath.Base(file),
	}
	err := debugLogRecordTemplate.Execute(l.output, record)
	MustCheck(err)
}

// Debug outputs a debug log message
func (l *BeeLogger) Debug(message string) {
	l.mustLogDebug(message)
}

// Debugf outputs a formatted debug log message
func (l *BeeLogger) Debugf(message string, vars ...interface{}) {
	l.mustLogDebug(message, vars...)
}

// Info outputs an information log message
func (l *BeeLogger) Info(message string) {
	l.mustLog(levelInfo, message)
}

// Infof outputs a formatted information log message
func (l *BeeLogger) Infof(message string, vars ...interface{}) {
	l.mustLog(levelInfo, message, vars...)
}

// Warn outputs a warning log message
func (l *BeeLogger) Warn(message string) {
	l.mustLog(levelWarn, message)
}

// Warnf outputs a formatted warning log message
func (l *BeeLogger) Warnf(message string, vars ...interface{}) {
	l.mustLog(levelWarn, message, vars...)
}

// Error outputs an error log message
func (l *BeeLogger) Error(message string) {
	l.mustLog(levelError, message)
}

// Errorf outputs a formatted error log message
func (l *BeeLogger) Errorf(message string, vars ...interface{}) {
	l.mustLog(levelError, message, vars...)
}

// Fatal outputs a fatal log message and exists
func (l *BeeLogger) Fatal(message string) {
	l.mustLog(levelFatal, message)
	os.Exit(255)
}

// Fatalf outputs a formatted log message and exists
func (l *BeeLogger) Fatalf(message string, vars ...interface{}) {
	l.mustLog(levelFatal, message, vars...)
	os.Exit(255)
}

// Success outputs a success log message
func (l *BeeLogger) Success(message string) {
	l.mustLog(levelSuccess, message)
}

// Successf outputs a formatted success log message
func (l *BeeLogger) Successf(message string, vars ...interface{}) {
	l.mustLog(levelSuccess, message, vars...)
}

// Hint outputs a hint log message
func (l *BeeLogger) Hint(message string) {
	l.mustLog(levelHint, message)
}

// Hintf outputs a formatted hint log message
func (l *BeeLogger) Hintf(message string, vars ...interface{}) {
	l.mustLog(levelHint, message, vars...)
}

// Critical outputs a critical log message
func (l *BeeLogger) Critical(message string) {
	l.mustLog(levelCritical, message)
}

// Criticalf outputs a formatted critical log message
func (l *BeeLogger) Criticalf(message string, vars ...interface{}) {
	l.mustLog(levelCritical, message, vars...)
}
<commit_msg>Included leading zeros for the log record ID<commit_after>// Copyright 2013 bee authors
//
// 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 main

import (
	"errors"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"runtime"
	"sync"
	"sync/atomic"
	"text/template"
)

var errInvalidLogLevel = errors.New("logger: invalid log level")

const (
	levelCritical = iota
	levelFatal
	levelSuccess
	levelHint
	levelDebug
	levelInfo
	levelWarn
	levelError
)

var (
	sequenceNo uint64
	logger     *BeeLogger
)

// BeeLogger logs logging records to the specified io.Writer
type BeeLogger struct {
	mu     sync.Mutex
	output io.Writer
}

// LogRecord represents a log record and contains the timestamp when the record
// was created, an increasing id, level and the actual formatted log line.
type LogRecord struct {
	ID       string
	Level    string
	Message  string
	Filename string
	LineNo   int
}

var (
	logRecordTemplate      *template.Template
	debugLogRecordTemplate *template.Template
	debugLogFormat         string
)

func init() {
	var (
		err             error
		simpleLogFormat = `{{Now "2006/01/02 15:04:05"}} {{.Level}} ▶ {{.ID}} {{.Message}}{{EndLine}}`
		debugLogFormat  = `{{Now "2006/01/02 15:04:05"}} {{.Level}} ▶ {{.ID}} {{.Filename}}:{{.LineNo}} {{.Message}}{{EndLine}}`
	)

	// Initialize and parse logging templates
	funcs := template.FuncMap{
		"Now":     Now,
		"EndLine": EndLine,
	}
	logRecordTemplate, err = template.New("logRecordTemplate").Funcs(funcs).Parse(simpleLogFormat)
	MustCheck(err)
	debugLogRecordTemplate, err = template.New("dbgLogRecordTemplate").Funcs(funcs).Parse(debugLogFormat)
	MustCheck(err)

	// Initialize the logger instance with a NewColorWriter output
	logger = &BeeLogger{output: NewColorWriter(os.Stdout)}
}

// SetOutput sets the logger output destination
func (l *BeeLogger) SetOutput(w io.Writer) {
	l.mu.Lock()
	defer l.mu.Unlock()
	l.output = NewColorWriter(w)
}

func (l *BeeLogger) getLevelTag(level int) string {
	switch level {
	case levelFatal:
		return "FATAL   "
	case levelSuccess:
		return "SUCCESS "
	case levelHint:
		return "HINT    "
	case levelDebug:
		return "DEBUG   "
	case levelInfo:
		return "INFO    "
	case levelWarn:
		return "WARN    "
	case levelError:
		return "ERROR   "
	case levelCritical:
		return "CRITICAL"
	default:
		panic(errInvalidLogLevel)
	}
}

func (l *BeeLogger) getColorLevel(level int) string {
	switch level {
	case levelCritical:
		return RedBold(l.getLevelTag(level))
	case levelFatal:
		return RedBold(l.getLevelTag(level))
	case levelInfo:
		return BlueBold(l.getLevelTag(level))
	case levelHint:
		return CyanBold(l.getLevelTag(level))
	case levelDebug:
		return YellowBold(l.getLevelTag(level))
	case levelError:
		return RedBold(l.getLevelTag(level))
	case levelWarn:
		return YellowBold(l.getLevelTag(level))
	case levelSuccess:
		return GreenBold(l.getLevelTag(level))
	default:
		panic(errInvalidLogLevel)
	}
}

// mustLog logs the message according to the specified level and arguments.
// It panics in case of an error.
func (l *BeeLogger) mustLog(level int, message string, args ...interface{}) {
	// Create the logging record and pass into the output
	record := LogRecord{
		ID:      fmt.Sprintf("%04d", atomic.AddUint64(&sequenceNo, 1)),
		Level:   l.getColorLevel(level),
		Message: fmt.Sprintf(message, args...),
	}

	err := logRecordTemplate.Execute(l.output, record)
	MustCheck(err)
}

// mustLogDebug logs a debug message only if debug mode
// is enabled. i.e. DEBUG_ENABLED="1"
func (l *BeeLogger) mustLogDebug(message string, args ...interface{}) {
	if !IsDebugEnabled() {
		return
	}

	// Change the output to Stderr
	l.SetOutput(os.Stderr)

	// Create the log record and Get the filename
	// and the line number of the caller
	_, file, line, _ := runtime.Caller(1)
	record := LogRecord{
		ID:       fmt.Sprintf("%04d", atomic.AddUint64(&sequenceNo, 1)),
		Level:    l.getColorLevel(levelDebug),
		Message:  fmt.Sprintf(message, args...),
		LineNo:   line,
		Filename: filepath.Base(file),
	}
	err := debugLogRecordTemplate.Execute(l.output, record)
	MustCheck(err)
}

// Debug outputs a debug log message
func (l *BeeLogger) Debug(message string) {
	l.mustLogDebug(message)
}

// Debugf outputs a formatted debug log message
func (l *BeeLogger) Debugf(message string, vars ...interface{}) {
	l.mustLogDebug(message, vars...)
}

// Info outputs an information log message
func (l *BeeLogger) Info(message string) {
	l.mustLog(levelInfo, message)
}

// Infof outputs a formatted information log message
func (l *BeeLogger) Infof(message string, vars ...interface{}) {
	l.mustLog(levelInfo, message, vars...)
}

// Warn outputs a warning log message
func (l *BeeLogger) Warn(message string) {
	l.mustLog(levelWarn, message)
}

// Warnf outputs a formatted warning log message
func (l *BeeLogger) Warnf(message string, vars ...interface{}) {
	l.mustLog(levelWarn, message, vars...)
}

// Error outputs an error log message
func (l *BeeLogger) Error(message string) {
	l.mustLog(levelError, message)
}

// Errorf outputs a formatted error log message
func (l *BeeLogger) Errorf(message string, vars ...interface{}) {
	l.mustLog(levelError, message, vars...)
}

// Fatal outputs a fatal log message and exists
func (l *BeeLogger) Fatal(message string) {
	l.mustLog(levelFatal, message)
	os.Exit(255)
}

// Fatalf outputs a formatted log message and exists
func (l *BeeLogger) Fatalf(message string, vars ...interface{}) {
	l.mustLog(levelFatal, message, vars...)
	os.Exit(255)
}

// Success outputs a success log message
func (l *BeeLogger) Success(message string) {
	l.mustLog(levelSuccess, message)
}

// Successf outputs a formatted success log message
func (l *BeeLogger) Successf(message string, vars ...interface{}) {
	l.mustLog(levelSuccess, message, vars...)
}

// Hint outputs a hint log message
func (l *BeeLogger) Hint(message string) {
	l.mustLog(levelHint, message)
}

// Hintf outputs a formatted hint log message
func (l *BeeLogger) Hintf(message string, vars ...interface{}) {
	l.mustLog(levelHint, message, vars...)
}

// Critical outputs a critical log message
func (l *BeeLogger) Critical(message string) {
	l.mustLog(levelCritical, message)
}

// Criticalf outputs a formatted critical log message
func (l *BeeLogger) Criticalf(message string, vars ...interface{}) {
	l.mustLog(levelCritical, message, vars...)
}
<|endoftext|>


<commit_before>package main

import (
	"os"
	"path/filepath"
	"runtime"
	"sort"
	"time"

	config "github.com/qiniu/logkit/conf"
	_ "github.com/qiniu/logkit/metric/all"
	"github.com/qiniu/logkit/mgr"
	"github.com/qiniu/logkit/times"
	"github.com/qiniu/logkit/utils"

	"net/http"
	_ "net/http/pprof"

	"github.com/labstack/echo"
	"github.com/qiniu/log"
)

//Config of logkit
type Config struct {
	MaxProcs         int      `json:"max_procs"`
	DebugLevel       int      `json:"debug_level"`
	ProfileHost      string   `json:"profile_host"`
	ConfsPath        []string `json:"confs_path"`
	CleanSelfLog     bool     `json:"clean_self_log"`
	CleanSelfDir     string   `json:"clean_self_dir"`
	CleanSelfPattern string   `json:"clean_self_pattern"`
	TimeLayouts      []string `json:"timeformat_layouts"`
	CleanSelfLogCnt  int      `json:"clean_self_cnt"`
	StaticRootPath   string   `json:"static_root_path"`
	mgr.ManagerConfig
}

var conf Config

const (
	Version           = "v1.2.3"
	defaultReserveCnt = 5
	defaultLogDir     = "./run"
	defaultLogPattern = "*.log-*"
)

func getValidPath(confPaths []string) (paths []string) {
	paths = make([]string, 0)
	exits := make(map[string]bool)
	for _, v := range confPaths {
		rp, err := filepath.Abs(v)
		if err != nil {
			log.Errorf("Get real path of ConfsPath %v error %v, ignore it", v, rp)
			continue
		}
		if _, ok := exits[rp]; ok {
			log.Errorf("ConfsPath %v duplicated, ignore", rp)
			continue
		}
		exits[rp] = true
		paths = append(paths, rp)
	}
	return
}

func cleanLogkitLog(dir, pattern string, reserveCnt int) {
	var err error
	path := filepath.Join(dir, pattern)
	matches, err := filepath.Glob(path)
	if err != nil {
		log.Errorf("filepath.Glob path %v error %v", path, err)
		return
	}
	if len(matches) <= reserveCnt {
		return
	}
	sort.Strings(matches)
	for _, f := range matches[0 : len(matches)-reserveCnt] {
		err := os.Remove(f)
		if err != nil {
			log.Errorf("Remove %s failed , error: %v", f, err)
			continue
		}
	}
	return
}

func loopCleanLogkitLog(dir, pattern string, reserveCnt int, exitchan chan struct{}) {
	if len(dir) <= 0 {
		dir = defaultLogDir
	}
	if len(pattern) <= 0 {
		pattern = defaultLogPattern
	}
	if reserveCnt <= 0 {
		reserveCnt = defaultReserveCnt
	}
	ticker := time.NewTicker(time.Minute * 10)
	for {
		select {
		case <-exitchan:
			return
		case <-ticker.C:
			cleanLogkitLog(dir, pattern, reserveCnt)
		}
	}
}

func main() {
	config.Init("f", "qbox", "qboxlogexporter.conf")
	if err := config.Load(&conf); err != nil {
		log.Fatal("config.Load failed:", err)
	}
	log.Printf("Welcome to use Logkit, Version: %v \n\nConfig: %#v", Version, conf)
	if conf.TimeLayouts != nil {
		times.AddLayout(conf.TimeLayouts)
	}
	if conf.MaxProcs == 0 {
		conf.MaxProcs = runtime.NumCPU()
	}
	runtime.GOMAXPROCS(conf.MaxProcs)
	log.SetOutputLevel(conf.DebugLevel)

	m, err := mgr.NewManager(conf.ManagerConfig)
	if err != nil {
		log.Fatalf("NewManager: %v", err)
	}
	paths := getValidPath(conf.ConfsPath)
	if len(paths) <= 0 {
		log.Fatalf("Cannot read or create any ConfsPath %v", conf.ConfsPath)
	}
	if err = m.Watch(paths); err != nil {
		log.Fatalf("watch path error %v", err)
	}
	m.RestoreWebDir()

	stopClean := make(chan struct{}, 0)
	defer close(stopClean)
	if conf.CleanSelfLog {
		go loopCleanLogkitLog(conf.CleanSelfDir, conf.CleanSelfPattern, conf.CleanSelfLogCnt, stopClean)
	}
	if len(conf.BindHost) > 0 {
		m.BindHost = conf.BindHost
	}
	e := echo.New()
	e.Static("/", conf.StaticRootPath)

	// start rest service
	rs := mgr.NewRestService(m, e)
	if conf.ProfileHost != "" {
		log.Printf("profile_host was open at %v", conf.ProfileHost)
		go func() {
			log.Println(http.ListenAndServe(conf.ProfileHost, nil))
		}()
	}
	utils.WaitForInterrupt(func() {
		rs.Stop()
		if conf.CleanSelfLog {
			stopClean <- struct{}{}
		}
		m.Stop()
	})
}
<commit_msg>fix crash without confs dir<commit_after>package main

import (
	"os"
	"path/filepath"
	"runtime"
	"sort"
	"time"

	config "github.com/qiniu/logkit/conf"
	_ "github.com/qiniu/logkit/metric/all"
	"github.com/qiniu/logkit/mgr"
	"github.com/qiniu/logkit/times"
	"github.com/qiniu/logkit/utils"

	"net/http"
	_ "net/http/pprof"

	"github.com/labstack/echo"
	"github.com/qiniu/log"
)

//Config of logkit
type Config struct {
	MaxProcs         int      `json:"max_procs"`
	DebugLevel       int      `json:"debug_level"`
	ProfileHost      string   `json:"profile_host"`
	ConfsPath        []string `json:"confs_path"`
	CleanSelfLog     bool     `json:"clean_self_log"`
	CleanSelfDir     string   `json:"clean_self_dir"`
	CleanSelfPattern string   `json:"clean_self_pattern"`
	TimeLayouts      []string `json:"timeformat_layouts"`
	CleanSelfLogCnt  int      `json:"clean_self_cnt"`
	StaticRootPath   string   `json:"static_root_path"`
	mgr.ManagerConfig
}

var conf Config

const (
	Version           = "v1.2.3"
	defaultReserveCnt = 5
	defaultLogDir     = "./run"
	defaultLogPattern = "*.log-*"
)

func getValidPath(confPaths []string) (paths []string) {
	paths = make([]string, 0)
	exits := make(map[string]bool)
	for _, v := range confPaths {
		rp, err := filepath.Abs(v)
		if err != nil {
			log.Errorf("Get real path of ConfsPath %v error %v, ignore it", v, rp)
			continue
		}
		if _, ok := exits[rp]; ok {
			log.Errorf("ConfsPath %v duplicated, ignore", rp)
			continue
		}
		exits[rp] = true
		paths = append(paths, rp)
	}
	return
}

func cleanLogkitLog(dir, pattern string, reserveCnt int) {
	var err error
	path := filepath.Join(dir, pattern)
	matches, err := filepath.Glob(path)
	if err != nil {
		log.Errorf("filepath.Glob path %v error %v", path, err)
		return
	}
	if len(matches) <= reserveCnt {
		return
	}
	sort.Strings(matches)
	for _, f := range matches[0 : len(matches)-reserveCnt] {
		err := os.Remove(f)
		if err != nil {
			log.Errorf("Remove %s failed , error: %v", f, err)
			continue
		}
	}
	return
}

func loopCleanLogkitLog(dir, pattern string, reserveCnt int, exitchan chan struct{}) {
	if len(dir) <= 0 {
		dir = defaultLogDir
	}
	if len(pattern) <= 0 {
		pattern = defaultLogPattern
	}
	if reserveCnt <= 0 {
		reserveCnt = defaultReserveCnt
	}
	ticker := time.NewTicker(time.Minute * 10)
	for {
		select {
		case <-exitchan:
			return
		case <-ticker.C:
			cleanLogkitLog(dir, pattern, reserveCnt)
		}
	}
}

func main() {
	config.Init("f", "qbox", "qboxlogexporter.conf")
	if err := config.Load(&conf); err != nil {
		log.Fatal("config.Load failed:", err)
	}
	log.Printf("Welcome to use Logkit, Version: %v \n\nConfig: %#v", Version, conf)
	if conf.TimeLayouts != nil {
		times.AddLayout(conf.TimeLayouts)
	}
	if conf.MaxProcs == 0 {
		conf.MaxProcs = runtime.NumCPU()
	}
	runtime.GOMAXPROCS(conf.MaxProcs)
	log.SetOutputLevel(conf.DebugLevel)

	m, err := mgr.NewManager(conf.ManagerConfig)
	if err != nil {
		log.Fatalf("NewManager: %v", err)
	}
	paths := getValidPath(conf.ConfsPath)
	if len(paths) <= 0 {
		log.Warnf("Cannot read or create any ConfsPath %v", conf.ConfsPath)
	}
	if err = m.Watch(paths); err != nil {
		log.Fatalf("watch path error %v", err)
	}
	m.RestoreWebDir()

	stopClean := make(chan struct{}, 0)
	defer close(stopClean)
	if conf.CleanSelfLog {
		go loopCleanLogkitLog(conf.CleanSelfDir, conf.CleanSelfPattern, conf.CleanSelfLogCnt, stopClean)
	}
	if len(conf.BindHost) > 0 {
		m.BindHost = conf.BindHost
	}
	e := echo.New()
	e.Static("/", conf.StaticRootPath)

	// start rest service
	rs := mgr.NewRestService(m, e)
	if conf.ProfileHost != "" {
		log.Printf("profile_host was open at %v", conf.ProfileHost)
		go func() {
			log.Println(http.ListenAndServe(conf.ProfileHost, nil))
		}()
	}
	utils.WaitForInterrupt(func() {
		rs.Stop()
		if conf.CleanSelfLog {
			stopClean <- struct{}{}
		}
		m.Stop()
	})
}
<|endoftext|>