mirror of
https://github.com/mumble-voip/grumble.git
synced 2025-12-19 21:59:59 -08:00
Merge branch 'master' into go_mod
This commit is contained in:
commit
9114380423
7 changed files with 213 additions and 127 deletions
10
Dockerfile
10
Dockerfile
|
|
@ -1,11 +1,12 @@
|
||||||
FROM golang:1.9-alpine as builder
|
FROM golang:1.14-alpine as builder
|
||||||
|
|
||||||
COPY . /go/src/mumble.info/grumble
|
COPY . /go/src/mumble.info/grumble
|
||||||
|
|
||||||
WORKDIR /go/src/mumble.info/grumble
|
WORKDIR /go/src/mumble.info/grumble
|
||||||
|
|
||||||
RUN apk add --no-cache git \
|
RUN apk add --no-cache git build-base
|
||||||
&& go get -v -t ./... \
|
|
||||||
|
RUN go get -v -t ./... \
|
||||||
&& go build mumble.info/grumble/cmd/grumble \
|
&& go build mumble.info/grumble/cmd/grumble \
|
||||||
&& go test -v ./...
|
&& go test -v ./...
|
||||||
|
|
||||||
|
|
@ -21,4 +22,7 @@ WORKDIR /data
|
||||||
|
|
||||||
VOLUME /data
|
VOLUME /data
|
||||||
|
|
||||||
|
EXPOSE 64738/tcp
|
||||||
|
EXPOSE 64738/udp
|
||||||
|
|
||||||
ENTRYPOINT [ "/usr/bin/grumble", "--datadir", "/data", "--log", "/data/grumble.log" ]
|
ENTRYPOINT [ "/usr/bin/grumble", "--datadir", "/data", "--log", "/data/grumble.log" ]
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
FROM arm32v6/golang:1.9-alpine as builder
|
FROM arm32v6/golang:1.14-alpine as builder
|
||||||
|
|
||||||
COPY . /go/src/mumble.info/grumble
|
COPY . /go/src/mumble.info/grumble
|
||||||
|
|
||||||
WORKDIR /go/src/mumble.info/grumble
|
WORKDIR /go/src/mumble.info/grumble
|
||||||
|
|
||||||
RUN apk add --no-cache git \
|
RUN apk add --no-cache git build-base
|
||||||
&& go get -v -t ./... \
|
|
||||||
|
RUN go get -v -t ./... \
|
||||||
&& go build mumble.info/grumble/cmd/grumble \
|
&& go build mumble.info/grumble/cmd/grumble \
|
||||||
&& go test -v ./...
|
&& go test -v ./...
|
||||||
|
|
||||||
|
|
@ -21,4 +22,7 @@ WORKDIR /data
|
||||||
|
|
||||||
VOLUME /data
|
VOLUME /data
|
||||||
|
|
||||||
|
EXPOSE 64738/tcp
|
||||||
|
EXPOSE 64738/udp
|
||||||
|
|
||||||
ENTRYPOINT [ "/usr/bin/grumble", "--datadir", "/data", "--log", "/data/grumble.log" ]
|
ENTRYPOINT [ "/usr/bin/grumble", "--datadir", "/data", "--log", "/data/grumble.log" ]
|
||||||
|
|
|
||||||
64
README.md
64
README.md
|
|
@ -1,7 +1,7 @@
|
||||||
|
|
||||||
Linux CI (Travis CI):
|
Linux CI (Travis CI):
|
||||||
|
|
||||||
[](https://travis-ci.org/mumble-voip/grumble)
|
[](https://travis-ci.com/mumble-voip/grumble)
|
||||||
|
|
||||||
Windows CI (AppVeyor):
|
Windows CI (AppVeyor):
|
||||||
|
|
||||||
|
|
@ -23,18 +23,21 @@ https://golang.org/dl/
|
||||||
Once Go is installed, you should set up a GOPATH to avoid clobbering your Go environment's root directory with third party packages.
|
Once Go is installed, you should set up a GOPATH to avoid clobbering your Go environment's root directory with third party packages.
|
||||||
|
|
||||||
Set up a GOPATH. On Unix, do something like this
|
Set up a GOPATH. On Unix, do something like this
|
||||||
|
```shell script
|
||||||
$ export GOPATH=$HOME/gocode
|
$ export GOPATH=$HOME/gocode
|
||||||
$ mkdir -p $GOPATH
|
$ mkdir -p $GOPATH
|
||||||
|
```
|
||||||
|
|
||||||
and on Windows, do something like this (for cmd.exe):
|
and on Windows, do something like this (for cmd.exe):
|
||||||
|
```shell script
|
||||||
c:\> set GOPATH=%USERPROFILE%\gocode
|
c:\> set GOPATH=%USERPROFILE%\gocode
|
||||||
c:\> mkdir %GOPATH%
|
c:\> mkdir %GOPATH%
|
||||||
|
```
|
||||||
|
|
||||||
Then, it's time to install Grumble. The following line should do the trick:
|
Then, it's time to install Grumble. The following line should do the trick:
|
||||||
|
```shell script
|
||||||
$ go get mumble.info/grumble/cmd/grumble
|
$ go get mumble.info/grumble/cmd/grumble
|
||||||
|
```
|
||||||
|
|
||||||
And that should be it. Grumble has been built, and is available in $GOPATH/bin as 'grumble'.
|
And that should be it. Grumble has been built, and is available in $GOPATH/bin as 'grumble'.
|
||||||
|
|
||||||
|
|
@ -61,29 +64,32 @@ Docker
|
||||||
## Getting the image
|
## Getting the image
|
||||||
|
|
||||||
### Building
|
### Building
|
||||||
|
```shell script
|
||||||
$ git clone https://github.com/mumble-voip/grumble.git
|
$ git clone https://github.com/mumble-voip/grumble.git
|
||||||
$ cd grumble/
|
$ cd grumble/
|
||||||
$ docker build -t mumble-voip/grumble .
|
$ docker build -t mumble-voip/grumble .
|
||||||
|
```
|
||||||
|
|
||||||
## Running
|
## Running
|
||||||
|
|
||||||
### Command line
|
### Command line
|
||||||
|
```shell script
|
||||||
$ docker run \
|
$ docker run \
|
||||||
-v $HOME/.grumble:/data \
|
-v $HOME/.grumble:/data \
|
||||||
-p 64738:64738 \
|
-p 64738:64738 \
|
||||||
-p 64738:64738/udp \
|
-p 64738:64738/udp \
|
||||||
mumble-voip/grumble
|
mumble-voip/grumble
|
||||||
|
```
|
||||||
|
|
||||||
### Compose
|
### Compose
|
||||||
|
```yaml
|
||||||
version: '3'
|
version: '3'
|
||||||
services:
|
services:
|
||||||
grumble:
|
grumble:
|
||||||
image: mumble-voip/grumble
|
image: mumble-voip/grumble
|
||||||
ports:
|
ports:
|
||||||
- 64738:64738
|
- 64738:64738
|
||||||
- 64738:64738/udp
|
- 64738:64738/udp
|
||||||
volumes:
|
volumes:
|
||||||
- $HOME/.grumble:/data
|
- $HOME/.grumble:/data
|
||||||
|
```
|
||||||
|
|
|
||||||
|
|
@ -37,14 +37,14 @@ func main() {
|
||||||
dataDir.Close()
|
dataDir.Close()
|
||||||
|
|
||||||
// Set up logging
|
// Set up logging
|
||||||
err = logtarget.Target.OpenFile(Args.LogPath)
|
logtarget.Default, err = logtarget.OpenFile(Args.LogPath, os.Stderr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Unable to open log file (%v): %v", Args.LogPath, err)
|
fmt.Fprintf(os.Stderr, "Unable to open log file (%v): %v", Args.LogPath, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.SetPrefix("[G] ")
|
log.SetPrefix("[G] ")
|
||||||
log.SetFlags(log.LstdFlags | log.Lmicroseconds)
|
log.SetFlags(log.LstdFlags | log.Lmicroseconds)
|
||||||
log.SetOutput(&logtarget.Target)
|
log.SetOutput(logtarget.Default)
|
||||||
log.Printf("Grumble")
|
log.Printf("Grumble")
|
||||||
log.Printf("Using data directory: %s", Args.DataDir)
|
log.Printf("Using data directory: %s", Args.DataDir)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -156,7 +156,7 @@ func NewServer(id int64) (s *Server, err error) {
|
||||||
s.Channels[0] = NewChannel(0, "Root")
|
s.Channels[0] = NewChannel(0, "Root")
|
||||||
s.nextChanId = 1
|
s.nextChanId = 1
|
||||||
|
|
||||||
s.Logger = log.New(&logtarget.Target, fmt.Sprintf("[%v] ", s.Id), log.LstdFlags|log.Lmicroseconds)
|
s.Logger = log.New(logtarget.Default, fmt.Sprintf("[%v] ", s.Id), log.LstdFlags|log.Lmicroseconds)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -175,8 +175,7 @@ func (server *Server) RootChannel() *Channel {
|
||||||
return root
|
return root
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set password as the new SuperUser password
|
func (server *Server) setConfigPassword(key, password string) {
|
||||||
func (server *Server) SetSuperUserPassword(password string) {
|
|
||||||
saltBytes := make([]byte, 24)
|
saltBytes := make([]byte, 24)
|
||||||
_, err := rand.Read(saltBytes)
|
_, err := rand.Read(saltBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -190,7 +189,6 @@ func (server *Server) SetSuperUserPassword(password string) {
|
||||||
digest := hex.EncodeToString(hasher.Sum(nil))
|
digest := hex.EncodeToString(hasher.Sum(nil))
|
||||||
|
|
||||||
// Could be racy, but shouldn't really matter...
|
// Could be racy, but shouldn't really matter...
|
||||||
key := "SuperUserPassword"
|
|
||||||
val := "sha1$" + salt + "$" + digest
|
val := "sha1$" + salt + "$" + digest
|
||||||
server.cfg.Set(key, val)
|
server.cfg.Set(key, val)
|
||||||
|
|
||||||
|
|
@ -199,9 +197,18 @@ func (server *Server) SetSuperUserPassword(password string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckSuperUserPassword checks whether password matches the set SuperUser password.
|
// SetSuperUserPassword sets password as the new SuperUser password
|
||||||
func (server *Server) CheckSuperUserPassword(password string) bool {
|
func (server *Server) SetSuperUserPassword(password string) {
|
||||||
parts := strings.Split(server.cfg.StringValue("SuperUserPassword"), "$")
|
server.setConfigPassword("SuperUserPassword", password)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetServerPassword sets password as the new Server password
|
||||||
|
func (server *Server) SetServerPassword(password string) {
|
||||||
|
server.setConfigPassword("ServerPassword", password)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (server *Server) checkConfigPassword(key, password string) bool {
|
||||||
|
parts := strings.Split(server.cfg.StringValue(key), "$")
|
||||||
if len(parts) != 3 {
|
if len(parts) != 3 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
@ -239,6 +246,20 @@ func (server *Server) CheckSuperUserPassword(password string) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CheckSuperUserPassword checks whether password matches the set SuperUser password.
|
||||||
|
func (server *Server) CheckSuperUserPassword(password string) bool {
|
||||||
|
return server.checkConfigPassword("SuperUserPassword", password)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckServerPassword checks whether password matches the set Server password.
|
||||||
|
func (server *Server) CheckServerPassword(password string) bool {
|
||||||
|
return server.checkConfigPassword("ServerPassword", password)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (server *Server) hasServerPassword() bool {
|
||||||
|
return server.cfg.StringValue("ServerPassword") != ""
|
||||||
|
}
|
||||||
|
|
||||||
// Called by the server to initiate a new client connection.
|
// Called by the server to initiate a new client connection.
|
||||||
func (server *Server) handleIncomingClient(conn net.Conn) (err error) {
|
func (server *Server) handleIncomingClient(conn net.Conn) (err error) {
|
||||||
client := new(Client)
|
client := new(Client)
|
||||||
|
|
@ -518,6 +539,13 @@ func (server *Server) handleAuthenticate(client *Client, msg *Message) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if client.user == nil && server.hasServerPassword() {
|
||||||
|
if auth.Password == nil || !server.CheckServerPassword(*auth.Password) {
|
||||||
|
client.RejectAuth(mumbleproto.Reject_WrongServerPW, "Invalid server password")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Setup the cryptstate for the client.
|
// Setup the cryptstate for the client.
|
||||||
err = client.crypt.GenerateKey(client.CryptoMode)
|
err = client.crypt.GenerateKey(client.CryptoMode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -1358,6 +1386,12 @@ func (server *Server) Port() int {
|
||||||
return port
|
return port
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ListenWebPort returns true if we should listen to the
|
||||||
|
// web port, otherwise false
|
||||||
|
func (server *Server) ListenWebPort() bool {
|
||||||
|
return !server.cfg.BoolValue("NoWebServer")
|
||||||
|
}
|
||||||
|
|
||||||
// WebPort returns the port the web server will listen on when it is
|
// WebPort returns the port the web server will listen on when it is
|
||||||
// started.
|
// started.
|
||||||
func (server *Server) WebPort() int {
|
func (server *Server) WebPort() int {
|
||||||
|
|
@ -1399,6 +1433,7 @@ func (server *Server) Start() (err error) {
|
||||||
host := server.HostAddress()
|
host := server.HostAddress()
|
||||||
port := server.Port()
|
port := server.Port()
|
||||||
webport := server.WebPort()
|
webport := server.WebPort()
|
||||||
|
shouldListenWeb := server.ListenWebPort()
|
||||||
|
|
||||||
// Setup our UDP listener
|
// Setup our UDP listener
|
||||||
server.udpconn, err = net.ListenUDP("udp", &net.UDPAddr{IP: net.ParseIP(host), Port: port})
|
server.udpconn, err = net.ListenUDP("udp", &net.UDPAddr{IP: net.ParseIP(host), Port: port})
|
||||||
|
|
@ -1437,37 +1472,42 @@ func (server *Server) Start() (err error) {
|
||||||
}
|
}
|
||||||
server.tlsl = tls.NewListener(server.tcpl, server.tlscfg)
|
server.tlsl = tls.NewListener(server.tcpl, server.tlscfg)
|
||||||
|
|
||||||
// Create HTTP server and WebSocket "listener"
|
if shouldListenWeb {
|
||||||
webaddr := &net.TCPAddr{IP: net.ParseIP(host), Port: webport}
|
// Create HTTP server and WebSocket "listener"
|
||||||
server.webtlscfg = &tls.Config{
|
webaddr := &net.TCPAddr{IP: net.ParseIP(host), Port: webport}
|
||||||
Certificates: []tls.Certificate{cert},
|
server.webtlscfg = &tls.Config{
|
||||||
ClientAuth: tls.NoClientCert,
|
Certificates: []tls.Certificate{cert},
|
||||||
NextProtos: []string{"http/1.1"},
|
ClientAuth: tls.NoClientCert,
|
||||||
}
|
NextProtos: []string{"http/1.1"},
|
||||||
server.webwsl = web.NewListener(webaddr, server.Logger)
|
|
||||||
mux := http.NewServeMux()
|
|
||||||
mux.Handle("/", server.webwsl)
|
|
||||||
server.webhttp = &http.Server{
|
|
||||||
Addr: webaddr.String(),
|
|
||||||
Handler: mux,
|
|
||||||
TLSConfig: server.webtlscfg,
|
|
||||||
ErrorLog: server.Logger,
|
|
||||||
|
|
||||||
// Set sensible timeouts, in case no reverse proxy is in front of Grumble.
|
|
||||||
// Non-conforming (or malicious) clients may otherwise block indefinitely and cause
|
|
||||||
// file descriptors (or handles, depending on your OS) to leak and/or be exhausted
|
|
||||||
ReadTimeout: 5 * time.Second,
|
|
||||||
WriteTimeout: 10 * time.Second,
|
|
||||||
IdleTimeout: 2 * time.Minute,
|
|
||||||
}
|
|
||||||
go func() {
|
|
||||||
err := server.webhttp.ListenAndServeTLS("", "")
|
|
||||||
if err != http.ErrServerClosed {
|
|
||||||
server.Fatalf("Fatal HTTP server error: %v", err)
|
|
||||||
}
|
}
|
||||||
}()
|
server.webwsl = web.NewListener(webaddr, server.Logger)
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
mux.Handle("/", server.webwsl)
|
||||||
|
server.webhttp = &http.Server{
|
||||||
|
Addr: webaddr.String(),
|
||||||
|
Handler: mux,
|
||||||
|
TLSConfig: server.webtlscfg,
|
||||||
|
ErrorLog: server.Logger,
|
||||||
|
|
||||||
|
// Set sensible timeouts, in case no reverse proxy is in front of Grumble.
|
||||||
|
// Non-conforming (or malicious) clients may otherwise block indefinitely and cause
|
||||||
|
// file descriptors (or handles, depending on your OS) to leak and/or be exhausted
|
||||||
|
ReadTimeout: 5 * time.Second,
|
||||||
|
WriteTimeout: 10 * time.Second,
|
||||||
|
IdleTimeout: 2 * time.Minute,
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
err := server.webhttp.ListenAndServeTLS("", "")
|
||||||
|
if err != http.ErrServerClosed {
|
||||||
|
server.Fatalf("Fatal HTTP server error: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
server.Printf("Started: listening on %v and %v", server.tcpl.Addr(), server.webwsl.Addr())
|
||||||
|
} else {
|
||||||
|
server.Printf("Started: listening on %v", server.tcpl.Addr())
|
||||||
|
}
|
||||||
|
|
||||||
server.Printf("Started: listening on %v and %v", server.tcpl.Addr(), server.webwsl.Addr())
|
|
||||||
server.running = true
|
server.running = true
|
||||||
|
|
||||||
// Open a fresh freezer log
|
// Open a fresh freezer log
|
||||||
|
|
@ -1490,10 +1530,17 @@ func (server *Server) Start() (err error) {
|
||||||
// for the servers. Each network goroutine defers a call to
|
// for the servers. Each network goroutine defers a call to
|
||||||
// netwg.Done(). In the Stop() we close all the connections
|
// netwg.Done(). In the Stop() we close all the connections
|
||||||
// and call netwg.Wait() to wait for the goroutines to end.
|
// and call netwg.Wait() to wait for the goroutines to end.
|
||||||
server.netwg.Add(3)
|
numWG := 2
|
||||||
|
if shouldListenWeb {
|
||||||
|
numWG++
|
||||||
|
}
|
||||||
|
|
||||||
|
server.netwg.Add(numWG)
|
||||||
go server.udpListenLoop()
|
go server.udpListenLoop()
|
||||||
go server.acceptLoop(server.tlsl)
|
go server.acceptLoop(server.tlsl)
|
||||||
go server.acceptLoop(server.webwsl)
|
if shouldListenWeb {
|
||||||
|
go server.acceptLoop(server.webwsl)
|
||||||
|
}
|
||||||
|
|
||||||
// Schedule a server registration update (if needed)
|
// Schedule a server registration update (if needed)
|
||||||
go func() {
|
go func() {
|
||||||
|
|
@ -1523,12 +1570,19 @@ func (server *Server) Stop() (err error) {
|
||||||
// This does not apply to opened WebSockets, which were forcibly closed when
|
// This does not apply to opened WebSockets, which were forcibly closed when
|
||||||
// all clients were disconnected.
|
// all clients were disconnected.
|
||||||
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(15*time.Second))
|
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(15*time.Second))
|
||||||
err = server.webhttp.Shutdown(ctx)
|
if server.ListenWebPort() {
|
||||||
cancel()
|
err = server.webhttp.Shutdown(ctx)
|
||||||
if err == context.DeadlineExceeded {
|
cancel()
|
||||||
server.Println("Forcibly shutdown HTTP server while stopping")
|
if err == context.DeadlineExceeded {
|
||||||
} else if err != nil {
|
server.Println("Forcibly shutdown HTTP server while stopping")
|
||||||
return err
|
} else if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = server.webwsl.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close the listeners
|
// Close the listeners
|
||||||
|
|
@ -1536,10 +1590,6 @@ func (server *Server) Stop() (err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = server.webwsl.Close()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close the UDP connection
|
// Close the UDP connection
|
||||||
err = server.udpconn.Close()
|
err = server.udpconn.Close()
|
||||||
|
|
@ -1565,3 +1615,8 @@ func (server *Server) Stop() (err error) {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set will set a configuration value
|
||||||
|
func (server *Server) Set(key string, value string) {
|
||||||
|
server.cfg.Set(key, value)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ func SignalHandler() {
|
||||||
signal.Notify(sigchan, syscall.SIGUSR2, syscall.SIGTERM, syscall.SIGINT)
|
signal.Notify(sigchan, syscall.SIGUSR2, syscall.SIGTERM, syscall.SIGINT)
|
||||||
for sig := range sigchan {
|
for sig := range sigchan {
|
||||||
if sig == syscall.SIGUSR2 {
|
if sig == syscall.SIGUSR2 {
|
||||||
err := logtarget.Target.Rotate()
|
err := logtarget.Default.Rotate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "unable to rotate log file: %v", err)
|
fmt.Fprintf(os.Stderr, "unable to rotate log file: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
package logtarget
|
package logtarget
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
@ -15,56 +15,72 @@ import (
|
||||||
// LogTarget to be registered with the regular Go log package.
|
// LogTarget to be registered with the regular Go log package.
|
||||||
// LogTarget multiplexes its incoming writes to multiple optional
|
// LogTarget multiplexes its incoming writes to multiple optional
|
||||||
// output writers, and one main output writer (the log file).
|
// output writers, and one main output writer (the log file).
|
||||||
type LogTarget struct {
|
type LogTarget interface {
|
||||||
mu sync.Mutex
|
io.Writer
|
||||||
logfn string
|
|
||||||
file *os.File
|
Rotate() error
|
||||||
memLog *bytes.Buffer
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var Target LogTarget
|
// logTarget is the default implementation of a log
|
||||||
|
// target. It can write to more than one writer at the same time
|
||||||
|
// but only rotate one file
|
||||||
|
type logTarget struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
logfn string
|
||||||
|
file *os.File
|
||||||
|
w io.Writer
|
||||||
|
ws []io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default is the default log target for the application
|
||||||
|
// It has to be initialized before used
|
||||||
|
var Default LogTarget
|
||||||
|
|
||||||
|
// OpenWriters returns a log target that will
|
||||||
|
// log to all the given writers at the same time
|
||||||
|
func OpenWriters(ws ...io.Writer) LogTarget {
|
||||||
|
target := &logTarget{}
|
||||||
|
target.w = io.MultiWriter(ws...)
|
||||||
|
return target
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenFile creates a LogTarget pointing to a log file
|
||||||
|
// and returns it.
|
||||||
|
// This method will open the file in append-only mode.
|
||||||
|
// It also takes a variable number of writers that are
|
||||||
|
// other log targets
|
||||||
|
func OpenFile(fileName string, ws ...io.Writer) (t LogTarget, err error) {
|
||||||
|
target := &logTarget{}
|
||||||
|
target.logfn = fileName
|
||||||
|
target.file, err = os.OpenFile(fileName, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0650)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
target.ws = ws
|
||||||
|
target.w = io.MultiWriter(append(ws, target.file)...)
|
||||||
|
return target, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Write writes a log message to all registered io.Writers
|
// Write writes a log message to all registered io.Writers
|
||||||
func (target *LogTarget) Write(in []byte) (int, error) {
|
func (target *logTarget) Write(out []byte) (int, error) {
|
||||||
|
target.mu.Lock()
|
||||||
|
defer target.mu.Unlock()
|
||||||
|
|
||||||
|
return target.Write(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rotate rotates the current log file, if one is opened.
|
||||||
|
// This method holds a lock while rotating the log file,
|
||||||
|
// and all log writes will be held back until the rotation
|
||||||
|
// is complete.
|
||||||
|
func (target *logTarget) Rotate() error {
|
||||||
target.mu.Lock()
|
target.mu.Lock()
|
||||||
defer target.mu.Unlock()
|
defer target.mu.Unlock()
|
||||||
|
|
||||||
if target.file == nil {
|
if target.file == nil {
|
||||||
panic("no log file opened")
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
n, err := os.Stderr.Write(in)
|
|
||||||
if err != nil {
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
|
|
||||||
n, err = target.file.Write(in)
|
|
||||||
if err != nil {
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return len(in), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpenFile opens the main log file for writing.
|
|
||||||
// This method will open the file in append-only mode.
|
|
||||||
func (target *LogTarget) OpenFile(fn string) (err error) {
|
|
||||||
target.logfn = fn
|
|
||||||
target.file, err = os.OpenFile(target.logfn, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0650)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rotate rotates the current log file.
|
|
||||||
// This method holds a lock while rotating the log file,
|
|
||||||
// and all log writes will be held back until the rotation
|
|
||||||
// is complete.
|
|
||||||
func (target *LogTarget) Rotate() error {
|
|
||||||
target.mu.Lock()
|
|
||||||
defer target.mu.Unlock()
|
|
||||||
|
|
||||||
// Close the existing log file
|
// Close the existing log file
|
||||||
err := target.file.Close()
|
err := target.file.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -75,6 +91,7 @@ func (target *LogTarget) Rotate() error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
target.w = io.MultiWriter(append(target.ws, target.file)...)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue