mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-19 10:22:23 +00:00
cmd, dashboard, internal, log: requested changes
This commit is contained in:
parent
1fbfccc79c
commit
64d4117af7
11 changed files with 255 additions and 248 deletions
|
|
@ -200,11 +200,11 @@ func init() {
|
||||||
app.Before = func(ctx *cli.Context) error {
|
app.Before = func(ctx *cli.Context) error {
|
||||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||||
|
|
||||||
disklogs := ""
|
logdir := ""
|
||||||
if ctx.GlobalBool(utils.DashboardEnabledFlag.Name) {
|
if ctx.GlobalBool(utils.DashboardEnabledFlag.Name) {
|
||||||
disklogs = (&node.Config{DataDir: utils.MakeDataDir(ctx)}).ResolvePath("logs")
|
logdir = (&node.Config{DataDir: utils.MakeDataDir(ctx)}).ResolvePath("logs")
|
||||||
}
|
}
|
||||||
if err := debug.Setup(ctx, disklogs); err != nil {
|
if err := debug.Setup(ctx, logdir); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// Cap the cache allowance and tune the garbage collector
|
// Cap the cache allowance and tune the garbage collector
|
||||||
|
|
|
||||||
|
|
@ -432,11 +432,7 @@ pv(1) tool to get a progress bar:
|
||||||
app.Flags = append(app.Flags, swarmmetrics.Flags...)
|
app.Flags = append(app.Flags, swarmmetrics.Flags...)
|
||||||
app.Before = func(ctx *cli.Context) error {
|
app.Before = func(ctx *cli.Context) error {
|
||||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||||
disklogs := ""
|
if err := debug.Setup(ctx, ""); err != nil {
|
||||||
if ctx.GlobalBool(utils.DashboardEnabledFlag.Name) {
|
|
||||||
disklogs = (&node.Config{DataDir: utils.MakeDataDir(ctx)}).ResolvePath("logs")
|
|
||||||
}
|
|
||||||
if err := debug.Setup(ctx, disklogs); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
swarmmetrics.Setup(ctx)
|
swarmmetrics.Setup(ctx)
|
||||||
|
|
|
||||||
|
|
@ -10389,12 +10389,16 @@ var _bundleJs = []byte((((((((((`!function(modules) {
|
||||||
default:
|
default:
|
||||||
lvl = "";
|
lvl = "";
|
||||||
}
|
}
|
||||||
if ("" === lvl || "string" != typeof t || t.length < 19 || "string" != typeof msg || !Array.isArray(ctx)) return void (content += '<span style="color:' + color + '">Invalid log record</span><br />');
|
var time = new Date(t);
|
||||||
ctx.length > 0 && (msg += " ".repeat(Math.max(40 - msg.length, 0))), content += '<span style="color:' + color + '">' + lvl + "</span>[" + t.substr(5, 5) + "|" + t.substr(11, 8) + "] " + msg;
|
if ("" === lvl || !(time instanceof Date) || isNaN(time) || "string" != typeof msg || !Array.isArray(ctx)) return void (content += '<span style="color:#ce3c23">Invalid log record</span><br />');
|
||||||
|
ctx.length > 0 && (msg += " ".repeat(Math.max(40 - msg.length, 0)));
|
||||||
|
var month = ("0" + (time.getMonth() + 1)).slice(-2), date = ("0" + time.getDate()).slice(-2), hours = ("0" + time.getHours()).slice(-2), minutes = ("0" + time.getMinutes()).slice(-2), seconds = ("0" + time.getSeconds()).slice(-2);
|
||||||
|
content += '<span style="color:' + color + '">' + lvl + "</span>[" + month + "-" + date + "|" + hours + ":" + minutes + ":" + seconds + "] " + msg;
|
||||||
for (var i = 0; i < ctx.length; i += 2) {
|
for (var i = 0; i < ctx.length; i += 2) {
|
||||||
var key = ctx[i], value = ctx[i + 1], padding = fieldPadding.get(key);
|
var key = ctx[i], val = ctx[i + 1], padding = fieldPadding.get(key);
|
||||||
(void 0 === padding || padding < value.length) && (padding = value.length, fieldPadding.set(key, padding)),
|
("number" != typeof padding || padding < val.length) && (padding = val.length, fieldPadding.set(key, padding));
|
||||||
content += ' <span style="color:' + color + '">' + key + "</span>=" + value + " ".repeat(padding - value.length);
|
var p = "";
|
||||||
|
i < ctx.length - 2 && (p = " ".repeat(padding - val.length)), content += ' <span style="color:' + color + '">' + key + "</span>=" + val + p;
|
||||||
}
|
}
|
||||||
content += "<br />";
|
content += "<br />";
|
||||||
}), content;
|
}), content;
|
||||||
|
|
@ -10403,19 +10407,19 @@ var _bundleJs = []byte((((((((((`!function(modules) {
|
||||||
if (prev.topChanged = 0, prev.bottomChanged = 0, !Array.isArray(update.chunk) || update.chunk.length < 1) return prev;
|
if (prev.topChanged = 0, prev.bottomChanged = 0, !Array.isArray(update.chunk) || update.chunk.length < 1) return prev;
|
||||||
Array.isArray(prev.chunks) || (prev.chunks = []);
|
Array.isArray(prev.chunks) || (prev.chunks = []);
|
||||||
var content = createChunk(update.chunk);
|
var content = createChunk(update.chunk);
|
||||||
if (!update.old) return prev.endBottom ? prev.chunks.length < 1 ? [ {
|
if (!update.source) return prev.endBottom ? prev.chunks.length < 1 ? [ {
|
||||||
content: content,
|
content: content,
|
||||||
name: "00000000000000.log"
|
name: "00000000000000.log"
|
||||||
} ] : (prev.chunks[prev.chunks.length - 1].content += content, prev.bottomChanged = 1,
|
} ] : (prev.chunks[prev.chunks.length - 1].content += content, prev.bottomChanged = 1,
|
||||||
prev) : prev;
|
prev) : prev;
|
||||||
var chunk = {
|
var chunk = {
|
||||||
content: content,
|
content: content,
|
||||||
name: update.old.name
|
name: update.source.name
|
||||||
};
|
};
|
||||||
return update.old.past ? (update.old.last && (prev.endTop = !0), prev.chunks.length >= limit && (prev.endBottom = !1,
|
return prev.chunks.length > 0 && update.source.name < prev.chunks[0].name ? (update.source.last && (prev.endTop = !0),
|
||||||
prev.chunks.splice(limit - 1, prev.chunks.length - limit + 1), prev.bottomChanged = -1),
|
prev.chunks.length >= limit && (prev.endBottom = !1, prev.chunks.splice(limit - 1, prev.chunks.length - limit + 1),
|
||||||
prev.chunks = [ chunk ].concat(_toConsumableArray(prev.chunks)), prev.topChanged = 1,
|
prev.bottomChanged = -1), prev.chunks = [ chunk ].concat(_toConsumableArray(prev.chunks)),
|
||||||
prev) : (update.old.last && (prev.endBottom = !0), prev.chunks.length >= limit && (prev.endTop = !1,
|
prev.topChanged = 1, prev) : (update.source.last && (prev.endBottom = !0), prev.chunks.length >= limit && (prev.endTop = !1,
|
||||||
prev.chunks.splice(0, prev.chunks.length - limit + 1), prev.topChanged = -1), prev.chunks = [].concat(_toConsumableArray(prev.chunks), [ chunk ]),
|
prev.chunks.splice(0, prev.chunks.length - limit + 1), prev.topChanged = -1), prev.chunks = [].concat(_toConsumableArray(prev.chunks), [ chunk ]),
|
||||||
prev.bottomChanged = 1, prev);
|
prev.bottomChanged = 1, prev);
|
||||||
};
|
};
|
||||||
|
|
@ -40575,7 +40579,7 @@ func bundleJs() (*asset, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
info := bindataFileInfo{name: "bundle.js", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)}
|
info := bindataFileInfo{name: "bundle.js", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)}
|
||||||
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x69, 0xa2, 0xe, 0x5a, 0x74, 0x8, 0xb1, 0x63, 0x1c, 0x5d, 0xae, 0x27, 0x7, 0x18, 0x9f, 0xbe, 0xee, 0x93, 0x92, 0x88, 0x93, 0xd6, 0xfd, 0xfd, 0x5b, 0xf, 0xe8, 0x45, 0xcb, 0x8f, 0xbe, 0x60}}
|
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xc1, 0xd0, 0xe9, 0xd3, 0xae, 0x7d, 0x57, 0x66, 0x4, 0xc8, 0xf9, 0x40, 0x68, 0xff, 0x8b, 0xa0, 0x4d, 0x0, 0xca, 0x4b, 0x3d, 0x7f, 0xb9, 0x40, 0xd, 0xae, 0xe0, 0x75, 0xd0, 0xd1, 0xfc, 0x7b}}
|
||||||
return a, nil
|
return a, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -67,25 +67,34 @@ const createChunk = (records: Array<Record>) => {
|
||||||
default:
|
default:
|
||||||
lvl = '';
|
lvl = '';
|
||||||
}
|
}
|
||||||
if (lvl === '' || typeof t !== 'string' || t.length < 19 || typeof msg !== 'string' || !Array.isArray(ctx)) {
|
const time = new Date(t);
|
||||||
content += `<span style="color:${color}">Invalid log record</span><br />`;
|
if (lvl === '' || !(time instanceof Date) || isNaN(time) || typeof msg !== 'string' || !Array.isArray(ctx)) {
|
||||||
|
content += '<span style="color:#ce3c23">Invalid log record</span><br />';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (ctx.length > 0) {
|
if (ctx.length > 0) {
|
||||||
msg += ' '.repeat(Math.max(40 - msg.length, 0));
|
msg += ' '.repeat(Math.max(40 - msg.length, 0));
|
||||||
}
|
}
|
||||||
// Time format: 2006-01-02T15:04:05-0700 -> 01-02|15:04:05
|
const month = `0${time.getMonth() + 1}`.slice(-2);
|
||||||
content += `<span style="color:${color}">${lvl}</span>[${t.substr(5, 5)}|${t.substr(11, 8)}] ${msg}`;
|
const date = `0${time.getDate()}`.slice(-2);
|
||||||
|
const hours = `0${time.getHours()}`.slice(-2);
|
||||||
|
const minutes = `0${time.getMinutes()}`.slice(-2);
|
||||||
|
const seconds = `0${time.getSeconds()}`.slice(-2);
|
||||||
|
content += `<span style="color:${color}">${lvl}</span>[${month}-${date}|${hours}:${minutes}:${seconds}] ${msg}`;
|
||||||
|
|
||||||
for (let i = 0; i < ctx.length; i += 2) {
|
for (let i = 0; i < ctx.length; i += 2) {
|
||||||
const key = ctx[i];
|
const key = ctx[i];
|
||||||
const value = ctx[i + 1];
|
const val = ctx[i + 1];
|
||||||
let padding = fieldPadding.get(key);
|
let padding = fieldPadding.get(key);
|
||||||
if (typeof padding === 'undefined' || padding < value.length) {
|
if (typeof padding !== 'number' || padding < val.length) {
|
||||||
padding = value.length;
|
padding = val.length;
|
||||||
fieldPadding.set(key, padding);
|
fieldPadding.set(key, padding);
|
||||||
}
|
}
|
||||||
content += ` <span style="color:${color}">${key}</span>=${value}${' '.repeat(padding - value.length)}`;
|
let p = '';
|
||||||
|
if (i < ctx.length - 2) {
|
||||||
|
p = ' '.repeat(padding - val.length);
|
||||||
|
}
|
||||||
|
content += ` <span style="color:${color}">${key}</span>=${val}${p}`;
|
||||||
}
|
}
|
||||||
content += '<br />';
|
content += '<br />';
|
||||||
});
|
});
|
||||||
|
|
@ -104,7 +113,7 @@ export const inserter = (limit: number) => (update: LogsMessage, prev: LogsType)
|
||||||
prev.chunks = [];
|
prev.chunks = [];
|
||||||
}
|
}
|
||||||
const content = createChunk(update.chunk);
|
const content = createChunk(update.chunk);
|
||||||
if (!update.old) {
|
if (!update.source) {
|
||||||
// In case of stream chunk.
|
// In case of stream chunk.
|
||||||
if (!prev.endBottom) {
|
if (!prev.endBottom) {
|
||||||
return prev;
|
return prev;
|
||||||
|
|
@ -119,10 +128,10 @@ export const inserter = (limit: number) => (update: LogsMessage, prev: LogsType)
|
||||||
}
|
}
|
||||||
const chunk = {
|
const chunk = {
|
||||||
content,
|
content,
|
||||||
name: update.old.name,
|
name: update.source.name,
|
||||||
};
|
};
|
||||||
if (update.old.past) {
|
if (prev.chunks.length > 0 && update.source.name < prev.chunks[0].name) {
|
||||||
if (update.old.last) {
|
if (update.source.last) {
|
||||||
prev.endTop = true;
|
prev.endTop = true;
|
||||||
}
|
}
|
||||||
if (prev.chunks.length >= limit) {
|
if (prev.chunks.length >= limit) {
|
||||||
|
|
@ -134,7 +143,7 @@ export const inserter = (limit: number) => (update: LogsMessage, prev: LogsType)
|
||||||
prev.topChanged = 1;
|
prev.topChanged = 1;
|
||||||
return prev;
|
return prev;
|
||||||
}
|
}
|
||||||
if (update.old.last) {
|
if (update.source.last) {
|
||||||
prev.endBottom = true;
|
prev.endBottom = true;
|
||||||
}
|
}
|
||||||
if (prev.chunks.length >= limit) {
|
if (prev.chunks.length >= limit) {
|
||||||
|
|
|
||||||
|
|
@ -86,12 +86,11 @@ export type Logs = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export type LogsMessage = {
|
export type LogsMessage = {
|
||||||
old: ?LogFile,
|
source: ?LogFile,
|
||||||
chunk: Array<Record>,
|
chunk: Array<Record>,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type LogFile = {
|
export type LogFile = {
|
||||||
name: string,
|
name: string,
|
||||||
past: string,
|
|
||||||
last: string,
|
last: string,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
"github.com/mohae/deepcopy"
|
"github.com/mohae/deepcopy"
|
||||||
"golang.org/x/net/websocket"
|
"golang.org/x/net/websocket"
|
||||||
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -243,15 +244,16 @@ func (db *Dashboard) apiHandler(conn *websocket.Conn) {
|
||||||
db.lock.Unlock()
|
db.lock.Unlock()
|
||||||
}()
|
}()
|
||||||
for {
|
for {
|
||||||
var r Request
|
r := new(Request)
|
||||||
err := websocket.JSON.Receive(conn, &r)
|
if err := websocket.JSON.Receive(conn, r); err != nil {
|
||||||
if err != nil {
|
if err != io.EOF {
|
||||||
client.logger.Warn("Failed to receive request", "err", err)
|
client.logger.Warn("Failed to receive request", "err", err)
|
||||||
|
}
|
||||||
close(done)
|
close(done)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if r.Logs != nil {
|
if r.Logs != nil {
|
||||||
db.handleLogRequest(r.Logs, client) // TODO (kurkomisi): concurrent function call?
|
db.handleLogRequest(r.Logs, client)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -292,8 +294,6 @@ func (db *Dashboard) collectData() {
|
||||||
|
|
||||||
frequency = float64(db.config.Refresh / time.Second)
|
frequency = float64(db.config.Refresh / time.Second)
|
||||||
numCPU = float64(runtime.NumCPU())
|
numCPU = float64(runtime.NumCPU())
|
||||||
|
|
||||||
sys = db.history.System
|
|
||||||
)
|
)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
|
@ -360,6 +360,8 @@ func (db *Dashboard) collectData() {
|
||||||
Time: now,
|
Time: now,
|
||||||
Value: float64(deltaDiskWrite) / frequency,
|
Value: float64(deltaDiskWrite) / frequency,
|
||||||
}
|
}
|
||||||
|
sys := db.history.System
|
||||||
|
db.lock.Lock()
|
||||||
sys.ActiveMemory = append(sys.ActiveMemory[1:], activeMemory)
|
sys.ActiveMemory = append(sys.ActiveMemory[1:], activeMemory)
|
||||||
sys.VirtualMemory = append(sys.VirtualMemory[1:], virtualMemory)
|
sys.VirtualMemory = append(sys.VirtualMemory[1:], virtualMemory)
|
||||||
sys.NetworkIngress = append(sys.NetworkIngress[1:], networkIngress)
|
sys.NetworkIngress = append(sys.NetworkIngress[1:], networkIngress)
|
||||||
|
|
@ -368,6 +370,7 @@ func (db *Dashboard) collectData() {
|
||||||
sys.SystemCPU = append(sys.SystemCPU[1:], systemCPU)
|
sys.SystemCPU = append(sys.SystemCPU[1:], systemCPU)
|
||||||
sys.DiskRead = append(sys.DiskRead[1:], diskRead)
|
sys.DiskRead = append(sys.DiskRead[1:], diskRead)
|
||||||
sys.DiskWrite = append(sys.DiskRead[1:], diskWrite)
|
sys.DiskWrite = append(sys.DiskRead[1:], diskWrite)
|
||||||
|
db.lock.Unlock()
|
||||||
|
|
||||||
db.sendToAll(&Message{
|
db.sendToAll(&Message{
|
||||||
System: &SystemMessage{
|
System: &SystemMessage{
|
||||||
|
|
|
||||||
222
dashboard/log.go
222
dashboard/log.go
|
|
@ -27,125 +27,121 @@ import (
|
||||||
"regexp"
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
"time"
|
"time"
|
||||||
|
"bytes"
|
||||||
)
|
)
|
||||||
|
|
||||||
// embrace inserts buf into brackets.
|
var emptyChunk = json.RawMessage("[]")
|
||||||
func embrace(buf []byte) []byte {
|
|
||||||
b := make([]byte, len(buf)+2)
|
// prepLogs creates a JSON array from the given log record buffer.
|
||||||
|
// Returns the prepared array and the position of the last '\n'
|
||||||
|
// character in the original buffer, or -1 if it doesn't contain any.
|
||||||
|
func prepLogs(buf []byte) (json.RawMessage, int) {
|
||||||
|
b := make(json.RawMessage, 1, len(buf) + 1)
|
||||||
b[0] = '['
|
b[0] = '['
|
||||||
copy(b[1:], buf)
|
b = append(b, buf...)
|
||||||
b[len(buf)+1] = ']'
|
|
||||||
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// replaceNewLinesWithCommas replaces the '\n' characters with ',' characters and returns the last replaced position.
|
|
||||||
func replaceNewLinesWithCommas(buf []byte) int {
|
|
||||||
last := -1
|
last := -1
|
||||||
for i := 0; i < len(buf); i++ {
|
for i := 1; i < len(b); i++ {
|
||||||
if buf[i] == '\n' {
|
if b[i] == '\n' {
|
||||||
buf[i] = ','
|
b[i] = ','
|
||||||
last = i
|
last = i
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return last
|
if last < 0 {
|
||||||
|
return emptyChunk, -1
|
||||||
|
}
|
||||||
|
b[last] = ']'
|
||||||
|
return b[:last+1], last - 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleLogRequest searches for the log file specified by the timestamp of the request, creates a JSON array out of it
|
// handleLogRequest searches for the log file specified by the timestamp of the
|
||||||
// and sends it to the requesting client.
|
// request, creates a JSON array out of it and sends it to the requesting client.
|
||||||
func (db *Dashboard) handleLogRequest(r *LogsRequest, c *client) {
|
func (db *Dashboard) handleLogRequest(r *LogsRequest, c *client) {
|
||||||
files, err := ioutil.ReadDir(db.logdir)
|
files, err := ioutil.ReadDir(db.logdir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn("Failed to open logdir", "path", db.logdir, "err", err)
|
log.Warn("Failed to open logdir", "path", db.logdir, "err", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
re := regexp.MustCompile(".log$")
|
re := regexp.MustCompile("\\.log$")
|
||||||
fileNames := make([]string, len(files))
|
fileNames := make([]string, 0, len(files))
|
||||||
n := 0
|
|
||||||
for _, f := range files {
|
for _, f := range files {
|
||||||
if f.Mode().IsRegular() && re.Match([]byte(f.Name())) {
|
if f.Mode().IsRegular() && re.MatchString(f.Name()) {
|
||||||
fileNames[n] = f.Name()
|
fileNames = append(fileNames, f.Name())
|
||||||
n++
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if n < 1 {
|
if len(fileNames) < 1 {
|
||||||
log.Warn("There isn't any log file in the logdir", "path", db.logdir)
|
log.Warn("No log files in logdir", "path", db.logdir)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
i := sort.Search(n, func(i int) bool {
|
idx := sort.Search(len(fileNames), func(idx int) bool {
|
||||||
return fileNames[i] >= r.Name // Returns the smallest index such as fileNames[i] >= r.Name.
|
// Returns the smallest index such as fileNames[idx] >= r.Name,
|
||||||
|
// if there is no such index, returns n.
|
||||||
|
return fileNames[idx] >= r.Name
|
||||||
})
|
})
|
||||||
|
|
||||||
if i >= n || fileNames[i] != r.Name {
|
switch {
|
||||||
log.Warn("The requested file isn't in the logdir", "path", filepath.Join(db.logdir, r.Name))
|
case idx < 0:
|
||||||
return
|
return
|
||||||
}
|
case idx == 0 && r.Past:
|
||||||
|
return
|
||||||
last := false
|
case idx >= len(fileNames):
|
||||||
if r.Past {
|
return
|
||||||
if i <= 0 {
|
case r.Past:
|
||||||
return
|
idx--
|
||||||
}
|
case idx == len(fileNames)-1 && fileNames[idx] == r.Name:
|
||||||
i--
|
return
|
||||||
if i == 0 {
|
case idx == len(fileNames)-1 || (idx == len(fileNames)-2 && fileNames[idx] == r.Name):
|
||||||
last = true
|
// The last file is continuously updated, and its chunks are streamed,
|
||||||
}
|
// so in order to avoid log record duplication on the client side, it is
|
||||||
} else {
|
// handled differently. Its actual content is always saved in the history.
|
||||||
if i >= n-1 {
|
db.lock.Lock()
|
||||||
return
|
if db.history.Logs != nil {
|
||||||
}
|
c.msg <- &Message{
|
||||||
if i == n-2 {
|
Logs: db.history.Logs,
|
||||||
// The last file is continuously updated, and its chunks are streamed,
|
|
||||||
// so in order to avoid log record duplication on the client side, it is
|
|
||||||
// handled differently. Its actual content is always saved in the history.
|
|
||||||
db.lock.Lock()
|
|
||||||
if db.history.Logs != nil {
|
|
||||||
c.msg <- &Message{
|
|
||||||
Logs: db.history.Logs,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
db.lock.Unlock()
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
i++
|
db.lock.Unlock()
|
||||||
|
return
|
||||||
|
case fileNames[idx] == r.Name:
|
||||||
|
idx++
|
||||||
}
|
}
|
||||||
|
|
||||||
path := filepath.Join(db.logdir, fileNames[i])
|
path := filepath.Join(db.logdir, fileNames[idx])
|
||||||
f, err := os.OpenFile(path, os.O_RDONLY, 0644)
|
|
||||||
if err != nil {
|
|
||||||
log.Warn("Failed to open file", "path", path, "err", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
var buf []byte
|
var buf []byte
|
||||||
if buf, err = ioutil.ReadAll(f); err != nil {
|
if buf, err = ioutil.ReadFile(path); err != nil {
|
||||||
log.Warn("Failed to read file", "path", path, "err", err)
|
log.Warn("Failed to read file", "path", path, "err", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
lastComma := replaceNewLinesWithCommas(buf)
|
chunk, end := prepLogs(buf)
|
||||||
if lastComma < 0 {
|
if end < 0 {
|
||||||
log.Warn("The file doesn't contain valid logs", "path", path)
|
log.Warn("The file doesn't contain valid logs", "path", path)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
db.lock.Lock()
|
|
||||||
c.msg <- &Message{
|
c.msg <- &Message{
|
||||||
Logs: &LogsMessage{
|
Logs: &LogsMessage{
|
||||||
Old: &LogFile{
|
Source: &LogFile{
|
||||||
Name: fileNames[i],
|
Name: fileNames[idx],
|
||||||
Past: r.Past,
|
Last: r.Past && idx == 0,
|
||||||
Last: last,
|
|
||||||
},
|
},
|
||||||
Chunk: embrace(buf[:lastComma]),
|
Chunk: chunk,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
db.lock.Unlock()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// streamLogs watches the file system, and when the logger writes the new log records into the files, picks them up,
|
// streamLogs watches the file system, and when the logger writes
|
||||||
// then makes JSON array out of them and sends them to the clients.
|
// the new log records into the files, picks them up, then makes
|
||||||
// This could be embedded into collectData, but they shouldn't depend on each other, and also cleaner this way.
|
// JSON array out of them and sends them to the clients.
|
||||||
func (db *Dashboard) streamLogs() {
|
func (db *Dashboard) streamLogs() {
|
||||||
defer db.wg.Done()
|
defer db.wg.Done()
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
errc chan error
|
||||||
|
)
|
||||||
|
defer func() {
|
||||||
|
if errc == nil {
|
||||||
|
errc = <-db.quit
|
||||||
|
}
|
||||||
|
errc <- err
|
||||||
|
}()
|
||||||
|
|
||||||
files, err := ioutil.ReadDir(db.logdir)
|
files, err := ioutil.ReadDir(db.logdir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -158,15 +154,16 @@ func (db *Dashboard) streamLogs() {
|
||||||
)
|
)
|
||||||
|
|
||||||
// The log records are always written into the last file in alphabetical order, because of the timestamp.
|
// The log records are always written into the last file in alphabetical order, because of the timestamp.
|
||||||
re := regexp.MustCompile(".log$")
|
re := regexp.MustCompile("\\.log$")
|
||||||
var i int
|
i := len(files) - 1
|
||||||
for i = len(files) - 1; i >= 0 && (!files[i].Mode().IsRegular() || !re.Match([]byte(files[i].Name()))); i-- {
|
for i >= 0 && (!files[i].Mode().IsRegular() || !re.MatchString(files[i].Name())) {
|
||||||
|
i--
|
||||||
}
|
}
|
||||||
if i < 0 {
|
if i < 0 {
|
||||||
log.Warn("There isn't any log file in the logdir", "path", db.logdir)
|
log.Warn("No log files in logdir", "path", db.logdir)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if opened, err = os.OpenFile(filepath.Join(db.logdir, files[i].Name()), os.O_RDONLY, 0644); err != nil {
|
if opened, err = os.OpenFile(filepath.Join(db.logdir, files[i].Name()), os.O_RDONLY, 0600); err != nil {
|
||||||
log.Warn("Failed to open file", "name", files[i].Name(), "err", err)
|
log.Warn("Failed to open file", "name", files[i].Name(), "err", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -178,12 +175,11 @@ func (db *Dashboard) streamLogs() {
|
||||||
}
|
}
|
||||||
db.lock.Lock()
|
db.lock.Lock()
|
||||||
db.history.Logs = &LogsMessage{
|
db.history.Logs = &LogsMessage{
|
||||||
Old: &LogFile{
|
Source: &LogFile{
|
||||||
Name: fi.Name(),
|
Name: fi.Name(),
|
||||||
Past: false,
|
|
||||||
Last: true,
|
Last: true,
|
||||||
},
|
},
|
||||||
Chunk: json.RawMessage("[]"),
|
Chunk: emptyChunk,
|
||||||
}
|
}
|
||||||
db.lock.Unlock()
|
db.lock.Unlock()
|
||||||
|
|
||||||
|
|
@ -202,7 +198,8 @@ func (db *Dashboard) streamLogs() {
|
||||||
ticker := time.NewTicker(db.config.Refresh)
|
ticker := time.NewTicker(db.config.Refresh)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
|
||||||
for {
|
loop:
|
||||||
|
for err == nil || errc == nil {
|
||||||
select {
|
select {
|
||||||
case event := <-watcher.Events:
|
case event := <-watcher.Events:
|
||||||
// Make sure that new log file was created.
|
// Make sure that new log file was created.
|
||||||
|
|
@ -211,9 +208,10 @@ func (db *Dashboard) streamLogs() {
|
||||||
}
|
}
|
||||||
if opened == nil {
|
if opened == nil {
|
||||||
log.Warn("The last log file is not opened")
|
log.Warn("The last log file is not opened")
|
||||||
return
|
break loop
|
||||||
}
|
}
|
||||||
// The new log file's name is always greater, because it is created using the actual log record's time.
|
// The new log file's name is always greater,
|
||||||
|
// because it is created using the actual log record's time.
|
||||||
if opened.Name() >= event.Name {
|
if opened.Name() >= event.Name {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
@ -221,25 +219,22 @@ func (db *Dashboard) streamLogs() {
|
||||||
chunk, err := ioutil.ReadAll(opened)
|
chunk, err := ioutil.ReadAll(opened)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn("Failed to read file", "name", opened.Name(), "err", err)
|
log.Warn("Failed to read file", "name", opened.Name(), "err", err)
|
||||||
return
|
break loop
|
||||||
}
|
}
|
||||||
b := make([]byte, len(buf)+len(chunk))
|
buf = append(buf, chunk...)
|
||||||
copy(b, buf)
|
|
||||||
copy(b[len(buf):], chunk)
|
|
||||||
buf = b
|
|
||||||
opened.Close()
|
opened.Close()
|
||||||
|
|
||||||
if last := replaceNewLinesWithCommas(buf); last >= 0 {
|
if chunk, last := prepLogs(buf); last >= 0 {
|
||||||
// Send the rest of the previously opened file.
|
// Send the rest of the previously opened file.
|
||||||
db.sendToAll(&Message{
|
db.sendToAll(&Message{
|
||||||
Logs: &LogsMessage{
|
Logs: &LogsMessage{
|
||||||
Chunk: embrace(buf[:last]),
|
Chunk: chunk,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if opened, err = os.OpenFile(event.Name, os.O_RDONLY, 0644); err != nil {
|
if opened, err = os.OpenFile(event.Name, os.O_RDONLY, 0644); err != nil {
|
||||||
log.Warn("Failed to open file", "name", event.Name, "err", err)
|
log.Warn("Failed to open file", "name", event.Name, "err", err)
|
||||||
return
|
break loop
|
||||||
}
|
}
|
||||||
buf = buf[:0]
|
buf = buf[:0]
|
||||||
|
|
||||||
|
|
@ -247,62 +242,55 @@ func (db *Dashboard) streamLogs() {
|
||||||
fi, err := opened.Stat()
|
fi, err := opened.Stat()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn("Problem with file", "name", opened.Name(), "err", err)
|
log.Warn("Problem with file", "name", opened.Name(), "err", err)
|
||||||
return
|
break loop
|
||||||
}
|
}
|
||||||
db.lock.Lock()
|
db.lock.Lock()
|
||||||
db.history.Logs.Old.Name = fi.Name()
|
db.history.Logs.Source.Name = fi.Name()
|
||||||
db.history.Logs.Chunk = json.RawMessage("[]")
|
db.history.Logs.Chunk = emptyChunk
|
||||||
db.lock.Unlock()
|
db.lock.Unlock()
|
||||||
case err := <-watcher.Errors:
|
case err := <-watcher.Errors:
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn("Fs watcher error", "err", err)
|
log.Warn("Fs watcher error", "err", err)
|
||||||
}
|
}
|
||||||
return
|
case <-ticker.C: // Send log updates to the client.
|
||||||
case errc := <-db.quit:
|
|
||||||
errc <- nil
|
|
||||||
return
|
|
||||||
// Send log updates to the client.
|
|
||||||
case <-ticker.C:
|
|
||||||
if opened == nil {
|
if opened == nil {
|
||||||
log.Warn("The last log file is not opened")
|
log.Warn("The last log file is not opened")
|
||||||
return
|
break loop
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read the new logs created since the last read.
|
// Read the new logs created since the last read.
|
||||||
chunk, err := ioutil.ReadAll(opened)
|
chunk, err := ioutil.ReadAll(opened)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn("Failed to read file", "name", opened.Name(), "err", err)
|
log.Warn("Failed to read file", "name", opened.Name(), "err", err)
|
||||||
return
|
break loop
|
||||||
}
|
}
|
||||||
b := make([]byte, len(buf)+len(chunk))
|
b := append(buf, chunk...)
|
||||||
copy(b, buf)
|
|
||||||
copy(b[len(buf):], chunk)
|
|
||||||
|
|
||||||
last := replaceNewLinesWithCommas(b)
|
chunk, last := prepLogs(b)
|
||||||
if last < 0 {
|
if last < 0 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
// Only keep the invalid part of the buffer, which can be valid after the next read.
|
// Only keep the invalid part of the buffer, which can be valid after the next read.
|
||||||
buf = b[last+1:]
|
buf = b[last+1:]
|
||||||
|
|
||||||
msg := embrace(b[:last])
|
|
||||||
var l *LogsMessage
|
var l *LogsMessage
|
||||||
// Update the history.
|
// Update the history.
|
||||||
db.lock.Lock()
|
db.lock.Lock()
|
||||||
if len(db.history.Logs.Chunk) == 2 {
|
if bytes.Equal(db.history.Logs.Chunk, emptyChunk) {
|
||||||
db.history.Logs.Chunk = msg
|
db.history.Logs.Chunk = chunk
|
||||||
l = deepcopy.Copy(db.history.Logs).(*LogsMessage)
|
l = deepcopy.Copy(db.history.Logs).(*LogsMessage)
|
||||||
} else {
|
} else {
|
||||||
b = make([]byte, len(db.history.Logs.Chunk)+len(msg)-1)
|
b = make([]byte, len(db.history.Logs.Chunk)+len(chunk)-1)
|
||||||
copy(b, db.history.Logs.Chunk)
|
copy(b, db.history.Logs.Chunk)
|
||||||
b[len(db.history.Logs.Chunk)-1] = ','
|
b[len(db.history.Logs.Chunk)-1] = ','
|
||||||
copy(b[len(db.history.Logs.Chunk):], msg[1:])
|
copy(b[len(db.history.Logs.Chunk):], chunk[1:])
|
||||||
db.history.Logs.Chunk = b
|
db.history.Logs.Chunk = b
|
||||||
l = &LogsMessage{Chunk: msg}
|
l = &LogsMessage{Chunk: chunk}
|
||||||
}
|
}
|
||||||
db.lock.Unlock()
|
db.lock.Unlock()
|
||||||
|
|
||||||
db.sendToAll(&Message{Logs: l})
|
db.sendToAll(&Message{Logs: l})
|
||||||
|
case errc = <-db.quit:
|
||||||
|
break loop
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -70,17 +70,19 @@ type SystemMessage struct {
|
||||||
DiskWrite ChartEntries `json:"diskWrite,omitempty"`
|
DiskWrite ChartEntries `json:"diskWrite,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LogsMessage wraps up a log chunk. If Source isn't present, the chunk is a stream chunk.
|
||||||
type LogsMessage struct {
|
type LogsMessage struct {
|
||||||
Old *LogFile `json:"old,omitempty"` // Attributes of the log file.
|
Source *LogFile `json:"source,omitempty"` // Attributes of the log file.
|
||||||
Chunk json.RawMessage `json:"chunk"` // Contains log records.
|
Chunk json.RawMessage `json:"chunk"` // Contains log records.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LogFile contains the attributes of a log file.
|
||||||
type LogFile struct {
|
type LogFile struct {
|
||||||
Name string `json:"name"` // The name of the file.
|
Name string `json:"name"` // The name of the file.
|
||||||
Past bool `json:"past"` // Denotes if the file is the previous or the next one.
|
Last bool `json:"last"` // Denotes if the actual log file is the last one in the directory.
|
||||||
Last bool `json:"last"` // Denotes if there isn't more file.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Request represents the client request.
|
||||||
type Request struct {
|
type Request struct {
|
||||||
Logs *LogsRequest `json:"logs,omitempty"`
|
Logs *LogsRequest `json:"logs,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -112,18 +112,19 @@ func init() {
|
||||||
|
|
||||||
// Setup initializes profiling and logging based on the CLI flags.
|
// Setup initializes profiling and logging based on the CLI flags.
|
||||||
// It should be called as early as possible in the program.
|
// It should be called as early as possible in the program.
|
||||||
func Setup(ctx *cli.Context, disklogs string) error {
|
func Setup(ctx *cli.Context, logdir string) error {
|
||||||
// logging
|
// logging
|
||||||
log.PrintOrigins(ctx.GlobalBool(debugFlag.Name))
|
log.PrintOrigins(ctx.GlobalBool(debugFlag.Name))
|
||||||
if disklogs != "" {
|
if logdir != "" {
|
||||||
glogger.SetHandler(log.MultiHandler(
|
rfh, err := log.RotatingFileHandler(
|
||||||
ostream,
|
logdir,
|
||||||
log.RotatingFileHandler(
|
262144,
|
||||||
disklogs,
|
log.JSONFormatOrderedEx(false, true),
|
||||||
262144,
|
)
|
||||||
log.JsonFormatOrderedCtx(false, true),
|
if err != nil {
|
||||||
),
|
return err
|
||||||
))
|
}
|
||||||
|
glogger.SetHandler(log.MultiHandler(ostream, rfh))
|
||||||
}
|
}
|
||||||
glogger.Verbosity(log.Lvl(ctx.GlobalInt(verbosityFlag.Name)))
|
glogger.Verbosity(log.Lvl(ctx.GlobalInt(verbosityFlag.Name)))
|
||||||
glogger.Vmodule(ctx.GlobalString(vmoduleFlag.Name))
|
glogger.Vmodule(ctx.GlobalString(vmoduleFlag.Name))
|
||||||
|
|
|
||||||
|
|
@ -202,16 +202,16 @@ func JSONFormat() Format {
|
||||||
return JSONFormatEx(false, true)
|
return JSONFormatEx(false, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// JsonFormatOrderedCtx is similar to JsonFormatEx, except instead of a map this creates an array out of the ctx
|
// JSONFormatOrderedEx formats log records as JSON arrays. If pretty is true,
|
||||||
// in order to keep the original order.
|
// records will be pretty-printed. If lineSeparated is true, records
|
||||||
func JsonFormatOrderedCtx(pretty, lineSeparated bool) Format {
|
// will be logged with a new line between each record.
|
||||||
|
func JSONFormatOrderedEx(pretty, lineSeparated bool) Format {
|
||||||
jsonMarshal := json.Marshal
|
jsonMarshal := json.Marshal
|
||||||
if pretty {
|
if pretty {
|
||||||
jsonMarshal = func(v interface{}) ([]byte, error) {
|
jsonMarshal = func(v interface{}) ([]byte, error) {
|
||||||
return json.MarshalIndent(v, "", " ")
|
return json.MarshalIndent(v, "", " ")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return FormatFunc(func(r *Record) []byte {
|
return FormatFunc(func(r *Record) []byte {
|
||||||
props := make(map[string]interface{})
|
props := make(map[string]interface{})
|
||||||
|
|
||||||
|
|
@ -226,7 +226,6 @@ func JsonFormatOrderedCtx(pretty, lineSeparated bool) Format {
|
||||||
props[errorKey] = fmt.Sprintf("%+v is not a string key,", r.Ctx[i])
|
props[errorKey] = fmt.Sprintf("%+v is not a string key,", r.Ctx[i])
|
||||||
}
|
}
|
||||||
ctx[i] = k
|
ctx[i] = k
|
||||||
// TODO (kurkomisi): display hash fields entirely - possibly logger independently
|
|
||||||
ctx[i+1] = formatLogfmtValue(r.Ctx[i+1], true)
|
ctx[i+1] = formatLogfmtValue(r.Ctx[i+1], true)
|
||||||
}
|
}
|
||||||
props[r.KeyNames.Ctx] = ctx
|
props[r.KeyNames.Ctx] = ctx
|
||||||
|
|
@ -238,11 +237,9 @@ func JsonFormatOrderedCtx(pretty, lineSeparated bool) Format {
|
||||||
})
|
})
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
if lineSeparated {
|
if lineSeparated {
|
||||||
b = append(b, '\n')
|
b = append(b, '\n')
|
||||||
}
|
}
|
||||||
|
|
||||||
return b
|
return b
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
142
log/handler.go
142
log/handler.go
|
|
@ -74,92 +74,100 @@ func FileHandler(path string, fmtr Format) (Handler, error) {
|
||||||
return closingHandler{f, StreamHandler(f, fmtr)}, nil
|
return closingHandler{f, StreamHandler(f, fmtr)}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type writeCounter struct {
|
// countingWriter wraps a WriteCloser object in order to count the written bytes.
|
||||||
w io.Writer
|
type countingWriter struct {
|
||||||
count uint
|
w io.WriteCloser // the wrapped object
|
||||||
|
count uint // number of bytes written
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *writeCounter) Write(p []byte) (n int, err error) {
|
// Write increments the byte counter by the number of bytes written.
|
||||||
|
// Implements the WriteCloser interface.
|
||||||
|
func (w *countingWriter) Write(p []byte) (n int, err error) {
|
||||||
n, err = w.w.Write(p)
|
n, err = w.w.Write(p)
|
||||||
w.count += uint(n)
|
w.count += uint(n)
|
||||||
return n, err
|
return n, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Close implements the WriteCloser interface.
|
||||||
|
func (w *countingWriter) Close() error {
|
||||||
|
return w.w.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepFile opens the log file at the given path, and cuts off the invalid part
|
||||||
|
// from the end, because the previous execution could have been finished by interruption.
|
||||||
|
// Assumes that every line ended by '\n' contains a valid log record.
|
||||||
|
func prepFile(path string) (*countingWriter, error) {
|
||||||
|
f, err := os.OpenFile(path, os.O_RDWR|os.O_APPEND, 0600)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, err = f.Seek(-1, io.SeekEnd)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
buf := make([]byte, 1)
|
||||||
|
var cut int64
|
||||||
|
for {
|
||||||
|
if _, err := f.Read(buf); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if buf[0] == '\n' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if _, err = f.Seek(-2, io.SeekCurrent); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cut++
|
||||||
|
}
|
||||||
|
fi, err := f.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ns := fi.Size() - cut
|
||||||
|
if err = f.Truncate(ns); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &countingWriter{w: f, count: uint(ns)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// RotatingFileHandler returns a handler which writes log records to file chunks
|
// RotatingFileHandler returns a handler which writes log records to file chunks
|
||||||
// at the given path. When a file's size reaches the limit, the handler creates
|
// at the given path. When a file's size reaches the limit, the handler creates
|
||||||
// a new file named after the timestamp of the first log record it will contain.
|
// a new file named after the timestamp of the first log record it will contain.
|
||||||
func RotatingFileHandler(path string, limit uint, formatter Format) Handler {
|
func RotatingFileHandler(path string, limit uint, formatter Format) (Handler, error) {
|
||||||
if err := os.MkdirAll(path, 0755); err != nil {
|
if err := os.MkdirAll(path, 0700); err != nil {
|
||||||
panic(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
files, err := ioutil.ReadDir(path)
|
files, err := ioutil.ReadDir(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return nil, err
|
||||||
|
}
|
||||||
|
re := regexp.MustCompile("\\.log$")
|
||||||
|
last := len(files) - 1
|
||||||
|
for last >= 0 && (!files[last].Mode().IsRegular() || !re.MatchString(files[last].Name())) {
|
||||||
|
last--
|
||||||
|
}
|
||||||
|
var counter *countingWriter
|
||||||
|
if last >= 0 && files[last].Size() < int64(limit){
|
||||||
|
// Open the last file, and continue to write into it until it's size reaches the limit.
|
||||||
|
if counter, err = prepFile(filepath.Join(path, files[last].Name())); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if counter == nil {
|
||||||
|
counter = new(countingWriter)
|
||||||
}
|
}
|
||||||
counter := new(writeCounter)
|
|
||||||
h := StreamHandler(counter, formatter)
|
h := StreamHandler(counter, formatter)
|
||||||
|
|
||||||
re := regexp.MustCompile(".log$")
|
|
||||||
var i int
|
|
||||||
for i = len(files) - 1; i >= 0 && (!files[i].Mode().IsRegular() || !re.Match([]byte(files[i].Name()))); i-- {
|
|
||||||
}
|
|
||||||
if i >= 0 {
|
|
||||||
// Open the last file, and continue to write into it until it's size reaches the limit.
|
|
||||||
last := files[i]
|
|
||||||
if last.Size() >= int64(limit) {
|
|
||||||
goto createNew
|
|
||||||
}
|
|
||||||
f, err := os.OpenFile(filepath.Join(path, last.Name()), os.O_RDWR|os.O_APPEND, 0644)
|
|
||||||
if err != nil {
|
|
||||||
goto createNew
|
|
||||||
}
|
|
||||||
// The previous execution could have been finished by interruption, in this case cut the invalid
|
|
||||||
// record from the end. Assume that every line ended by '\n' contains a valid log record.
|
|
||||||
bufSize := int64(100)
|
|
||||||
buf := make([]byte, bufSize)
|
|
||||||
cut := bufSize
|
|
||||||
if _, err = f.Seek(-bufSize, 2); err != nil {
|
|
||||||
goto createNew
|
|
||||||
}
|
|
||||||
n, err := f.Read(buf)
|
|
||||||
for err == nil {
|
|
||||||
for ; n > 0 && buf[n-1] != '\n'; n-- {
|
|
||||||
}
|
|
||||||
if n > 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if _, err = f.Seek(-2*bufSize, 1); err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
cut += bufSize
|
|
||||||
n, err = f.Read(buf)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
goto createNew
|
|
||||||
}
|
|
||||||
cut -= int64(n)
|
|
||||||
|
|
||||||
ns := last.Size() - cut
|
|
||||||
if err = f.Truncate(ns); err != nil {
|
|
||||||
goto createNew
|
|
||||||
}
|
|
||||||
counter.w = f
|
|
||||||
counter.count = uint(ns)
|
|
||||||
}
|
|
||||||
createNew:
|
|
||||||
|
|
||||||
return FuncHandler(func(r *Record) error {
|
return FuncHandler(func(r *Record) error {
|
||||||
if counter.count > limit || counter.w == nil {
|
if counter.count > limit {
|
||||||
// TODO (kurkomisi): close the last file too.
|
counter.Close()
|
||||||
if f, ok := counter.w.(*os.File); ok {
|
counter.w = nil
|
||||||
if err := f.Close(); err != nil {
|
}
|
||||||
return err
|
if counter.w == nil {
|
||||||
}
|
|
||||||
}
|
|
||||||
f, err := os.OpenFile(
|
f, err := os.OpenFile(
|
||||||
filepath.Join(path, fmt.Sprintf("%s.log", strings.Replace(r.Time.Format("060102150405.00"), ".", "", 1))),
|
filepath.Join(path, fmt.Sprintf("%s.log", strings.Replace(r.Time.Format("060102150405.00"), ".", "", 1))),
|
||||||
os.O_CREATE|os.O_APPEND|os.O_WRONLY,
|
os.O_CREATE|os.O_APPEND|os.O_WRONLY,
|
||||||
0644,
|
0600,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -168,7 +176,7 @@ createNew:
|
||||||
counter.count = 0
|
counter.count = 0
|
||||||
}
|
}
|
||||||
return h.Log(r)
|
return h.Log(r)
|
||||||
})
|
}), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NetHandler opens a socket to the given address and writes records
|
// NetHandler opens a socket to the given address and writes records
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue